summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
Diffstat (limited to 'drivers')
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp45
-rw-r--r--drivers/metal/metal_objects.h267
-rw-r--r--drivers/metal/metal_objects.mm172
-rw-r--r--drivers/metal/rendering_context_driver_metal.mm6
-rw-r--r--drivers/metal/rendering_device_driver_metal.h8
-rw-r--r--drivers/metal/rendering_device_driver_metal.mm72
-rw-r--r--drivers/unix/file_access_unix.cpp41
-rw-r--r--drivers/unix/file_access_unix.h4
-rw-r--r--drivers/unix/net_socket_unix.cpp (renamed from drivers/unix/net_socket_posix.cpp)262
-rw-r--r--drivers/unix/net_socket_unix.h (renamed from drivers/unix/net_socket_posix.h)74
-rw-r--r--drivers/unix/os_unix.cpp2
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp22
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.h1
-rw-r--r--drivers/windows/file_access_windows.cpp2
-rw-r--r--drivers/windows/net_socket_winsock.cpp613
-rw-r--r--drivers/windows/net_socket_winsock.h102
16 files changed, 1230 insertions, 463 deletions
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index 3c959f0143..0138f99d50 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -1654,28 +1654,39 @@ void RasterizerCanvasGLES3::light_update_shadow(RID p_rid, int p_shadow_index, c
return;
}
- for (int i = 0; i < 4; i++) {
- glViewport((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
+ Projection projection;
+ {
+ real_t fov = 90;
+ real_t nearp = p_near;
+ real_t farp = p_far;
+ real_t aspect = 1.0;
- Projection projection;
- {
- real_t fov = 90;
- real_t nearp = p_near;
- real_t farp = p_far;
- real_t aspect = 1.0;
+ real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5));
+ real_t ymin = -ymax;
+ real_t xmin = ymin * aspect;
+ real_t xmax = ymax * aspect;
- real_t ymax = nearp * Math::tan(Math::deg_to_rad(fov * 0.5));
- real_t ymin = -ymax;
- real_t xmin = ymin * aspect;
- real_t xmax = ymax * aspect;
+ projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp);
+ }
- projection.set_frustum(xmin, xmax, ymin, ymax, nearp, farp);
- }
+ // Precomputed:
+ // Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0));
+ // projection = projection * Projection(Transform3D().looking_at(cam_targets[i], Vector3(0, 0, -1)).affine_inverse());
+ const Projection projections[4] = {
+ projection * Projection(Vector4(0, 0, -1, 0), Vector4(1, 0, 0, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
- Vector3 cam_target = Basis::from_euler(Vector3(0, 0, Math_TAU * ((i + 3) / 4.0))).xform(Vector3(0, 1, 0));
+ projection * Projection(Vector4(-1, 0, 0, 0), Vector4(0, 0, -1, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
+
+ projection * Projection(Vector4(0, 0, 1, 0), Vector4(-1, 0, 0, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1)),
+
+ projection * Projection(Vector4(1, 0, 0, 0), Vector4(0, 0, 1, 0), Vector4(0, -1, 0, 0), Vector4(0, 0, 0, 1))
+
+ };
+
+ for (int i = 0; i < 4; i++) {
+ glViewport((state.shadow_texture_size / 4) * i, p_shadow_index * 2, (state.shadow_texture_size / 4), 2);
- projection = projection * Projection(Transform3D().looking_at(cam_target, Vector3(0, 0, -1)).affine_inverse());
- shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, projection, shadow_render.shader_version, variant);
+ shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::PROJECTION, projections[i], shadow_render.shader_version, variant);
static const Vector2 directions[4] = { Vector2(1, 0), Vector2(0, 1), Vector2(-1, 0), Vector2(0, -1) };
shadow_render.shader.version_set_uniform(CanvasOcclusionShaderGLES3::DIRECTION, directions[i].x, directions[i].y, shadow_render.shader_version, variant);
diff --git a/drivers/metal/metal_objects.h b/drivers/metal/metal_objects.h
index fd7d93bbbd..5eda826d2a 100644
--- a/drivers/metal/metal_objects.h
+++ b/drivers/metal/metal_objects.h
@@ -82,6 +82,9 @@ MTL_CLASS(Texture)
} //namespace MTL
+/// Metal buffer index for the view mask when rendering multi-view.
+const uint32_t VIEW_MASK_BUFFER_INDEX = 24;
+
enum ShaderStageUsage : uint32_t {
None = 0,
Vertex = RDD::SHADER_STAGE_VERTEX_BIT,
@@ -142,6 +145,12 @@ struct ClearAttKey {
const static uint32_t STENCIL_INDEX = DEPTH_INDEX + 1;
const static uint32_t ATTACHMENT_COUNT = STENCIL_INDEX + 1;
+ enum Flags : uint16_t {
+ CLEAR_FLAGS_NONE = 0,
+ CLEAR_FLAGS_LAYERED = 1 << 0,
+ };
+
+ Flags flags = CLEAR_FLAGS_NONE;
uint16_t sample_count = 0;
uint16_t pixel_formats[ATTACHMENT_COUNT] = { 0 };
@@ -150,19 +159,22 @@ struct ClearAttKey {
_FORCE_INLINE_ void set_stencil_format(MTLPixelFormat p_fmt) { pixel_formats[STENCIL_INDEX] = p_fmt; }
_FORCE_INLINE_ MTLPixelFormat depth_format() const { return (MTLPixelFormat)pixel_formats[DEPTH_INDEX]; }
_FORCE_INLINE_ MTLPixelFormat stencil_format() const { return (MTLPixelFormat)pixel_formats[STENCIL_INDEX]; }
+ _FORCE_INLINE_ void enable_layered_rendering() { flags::set(flags, CLEAR_FLAGS_LAYERED); }
_FORCE_INLINE_ bool is_enabled(uint32_t p_idx) const { return pixel_formats[p_idx] != 0; }
_FORCE_INLINE_ bool is_depth_enabled() const { return pixel_formats[DEPTH_INDEX] != 0; }
_FORCE_INLINE_ bool is_stencil_enabled() const { return pixel_formats[STENCIL_INDEX] != 0; }
+ _FORCE_INLINE_ bool is_layered_rendering_enabled() const { return flags::any(flags, CLEAR_FLAGS_LAYERED); }
_FORCE_INLINE_ bool operator==(const ClearAttKey &p_rhs) const {
return memcmp(this, &p_rhs, sizeof(ClearAttKey)) == 0;
}
uint32_t hash() const {
- uint32_t h = hash_murmur3_one_32(sample_count);
+ uint32_t h = hash_murmur3_one_32(flags);
+ h = hash_murmur3_one_32(sample_count, h);
h = hash_murmur3_buffer(pixel_formats, ATTACHMENT_COUNT * sizeof(pixel_formats[0]), h);
- return h;
+ return hash_fmix32(h);
}
};
@@ -206,6 +218,97 @@ public:
~MDResourceCache() = default;
};
+enum class MDAttachmentType : uint8_t {
+ None = 0,
+ Color = 1 << 0,
+ Depth = 1 << 1,
+ Stencil = 1 << 2,
+};
+
+_FORCE_INLINE_ MDAttachmentType &operator|=(MDAttachmentType &p_a, MDAttachmentType p_b) {
+ flags::set(p_a, p_b);
+ return p_a;
+}
+
+_FORCE_INLINE_ bool operator&(MDAttachmentType p_a, MDAttachmentType p_b) {
+ return uint8_t(p_a) & uint8_t(p_b);
+}
+
+struct MDSubpass {
+ uint32_t subpass_index = 0;
+ uint32_t view_count = 0;
+ LocalVector<RDD::AttachmentReference> input_references;
+ LocalVector<RDD::AttachmentReference> color_references;
+ RDD::AttachmentReference depth_stencil_reference;
+ LocalVector<RDD::AttachmentReference> resolve_references;
+
+ MTLFmtCaps getRequiredFmtCapsForAttachmentAt(uint32_t p_index) const;
+};
+
+struct API_AVAILABLE(macos(11.0), ios(14.0)) MDAttachment {
+private:
+ uint32_t index = 0;
+ uint32_t firstUseSubpassIndex = 0;
+ uint32_t lastUseSubpassIndex = 0;
+
+public:
+ MTLPixelFormat format = MTLPixelFormatInvalid;
+ MDAttachmentType type = MDAttachmentType::None;
+ MTLLoadAction loadAction = MTLLoadActionDontCare;
+ MTLStoreAction storeAction = MTLStoreActionDontCare;
+ MTLLoadAction stencilLoadAction = MTLLoadActionDontCare;
+ MTLStoreAction stencilStoreAction = MTLStoreActionDontCare;
+ uint32_t samples = 1;
+
+ /*!
+ * @brief Returns true if this attachment is first used in the given subpass.
+ * @param p_subpass
+ * @return
+ */
+ _FORCE_INLINE_ bool isFirstUseOf(MDSubpass const &p_subpass) const {
+ return p_subpass.subpass_index == firstUseSubpassIndex;
+ }
+
+ /*!
+ * @brief Returns true if this attachment is last used in the given subpass.
+ * @param p_subpass
+ * @return
+ */
+ _FORCE_INLINE_ bool isLastUseOf(MDSubpass const &p_subpass) const {
+ return p_subpass.subpass_index == lastUseSubpassIndex;
+ }
+
+ void linkToSubpass(MDRenderPass const &p_pass);
+
+ MTLStoreAction getMTLStoreAction(MDSubpass const &p_subpass,
+ bool p_is_rendering_entire_area,
+ bool p_has_resolve,
+ bool p_can_resolve,
+ bool p_is_stencil) const;
+ bool configureDescriptor(MTLRenderPassAttachmentDescriptor *p_desc,
+ PixelFormats &p_pf,
+ MDSubpass const &p_subpass,
+ id<MTLTexture> p_attachment,
+ bool p_is_rendering_entire_area,
+ bool p_has_resolve,
+ bool p_can_resolve,
+ bool p_is_stencil) const;
+ /** Returns whether this attachment should be cleared in the subpass. */
+ bool shouldClear(MDSubpass const &p_subpass, bool p_is_stencil) const;
+};
+
+class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPass {
+public:
+ Vector<MDAttachment> attachments;
+ Vector<MDSubpass> subpasses;
+
+ uint32_t get_sample_count() const {
+ return attachments.is_empty() ? 1 : attachments[0].samples;
+ }
+
+ MDRenderPass(Vector<MDAttachment> &p_attachments, Vector<MDSubpass> &p_subpasses);
+};
+
class API_AVAILABLE(macos(11.0), ios(14.0)) MDCommandBuffer {
private:
RenderingDeviceDriverMetal *device_driver = nullptr;
@@ -220,8 +323,8 @@ private:
void _render_set_dirty_state();
void _render_bind_uniform_sets();
- static void _populate_vertices(simd::float4 *p_vertices, Size2i p_fb_size, VectorView<Rect2i> p_rects);
- static uint32_t _populate_vertices(simd::float4 *p_vertices, uint32_t p_index, Rect2i const &p_rect, Size2i p_fb_size);
+ void _populate_vertices(simd::float4 *p_vertices, Size2i p_fb_size, VectorView<Rect2i> p_rects);
+ uint32_t _populate_vertices(simd::float4 *p_vertices, uint32_t p_index, Rect2i const &p_rect, Size2i p_fb_size);
void _end_render_pass();
void _render_clear_render_area();
@@ -268,34 +371,14 @@ public:
// Bit mask of the uniform sets that are dirty, to prevent redundant binding.
uint64_t uniform_set_mask = 0;
- _FORCE_INLINE_ void reset() {
- pass = nil;
- frameBuffer = nil;
- pipeline = nil;
- current_subpass = UINT32_MAX;
- render_area = {};
- is_rendering_entire_area = false;
- desc = nil;
- encoder = nil;
- index_buffer = nil;
- index_type = MTLIndexTypeUInt16;
- dirty = DIRTY_NONE;
- uniform_sets.clear();
- uniform_set_mask = 0;
- clear_values.clear();
- viewports.clear();
- scissors.clear();
- blend_constants.reset();
- vertex_buffers.clear();
- vertex_offsets.clear();
- // Keep the keys, as they are likely to be used again.
- for (KeyValue<StageResourceUsage, LocalVector<__unsafe_unretained id<MTLResource>>> &kv : resource_usage) {
- kv.value.clear();
- }
- }
-
+ _FORCE_INLINE_ void reset();
void end_encoding();
+ _ALWAYS_INLINE_ const MDSubpass &get_subpass() const {
+ DEV_ASSERT(pass != nullptr);
+ return pass->subpasses[current_subpass];
+ }
+
_FORCE_INLINE_ void mark_viewport_dirty() {
if (viewports.is_empty()) {
return;
@@ -649,6 +732,7 @@ public:
uint32_t size = 0;
} frag;
} push_constants;
+ bool needs_view_mask_buffer = false;
MDLibrary *vert;
MDLibrary *frag;
@@ -659,7 +743,10 @@ public:
void encode_push_constant_data(VectorView<uint32_t> p_data, MDCommandBuffer *p_cb) final;
- MDRenderShader(CharString p_name, Vector<UniformSet> p_sets, MDLibrary *p_vert, MDLibrary *p_frag);
+ MDRenderShader(CharString p_name,
+ bool p_needs_view_mask_buffer,
+ Vector<UniformSet> p_sets,
+ MDLibrary *p_vert, MDLibrary *p_frag);
};
_FORCE_INLINE_ StageResourceUsage &operator|=(StageResourceUsage &p_a, uint32_t p_b) {
@@ -702,96 +789,6 @@ public:
BoundUniformSet &boundUniformSetForShader(MDShader *p_shader, id<MTLDevice> p_device);
};
-enum class MDAttachmentType : uint8_t {
- None = 0,
- Color = 1 << 0,
- Depth = 1 << 1,
- Stencil = 1 << 2,
-};
-
-_FORCE_INLINE_ MDAttachmentType &operator|=(MDAttachmentType &p_a, MDAttachmentType p_b) {
- flags::set(p_a, p_b);
- return p_a;
-}
-
-_FORCE_INLINE_ bool operator&(MDAttachmentType p_a, MDAttachmentType p_b) {
- return uint8_t(p_a) & uint8_t(p_b);
-}
-
-struct MDSubpass {
- uint32_t subpass_index = 0;
- LocalVector<RDD::AttachmentReference> input_references;
- LocalVector<RDD::AttachmentReference> color_references;
- RDD::AttachmentReference depth_stencil_reference;
- LocalVector<RDD::AttachmentReference> resolve_references;
-
- MTLFmtCaps getRequiredFmtCapsForAttachmentAt(uint32_t p_index) const;
-};
-
-struct API_AVAILABLE(macos(11.0), ios(14.0)) MDAttachment {
-private:
- uint32_t index = 0;
- uint32_t firstUseSubpassIndex = 0;
- uint32_t lastUseSubpassIndex = 0;
-
-public:
- MTLPixelFormat format = MTLPixelFormatInvalid;
- MDAttachmentType type = MDAttachmentType::None;
- MTLLoadAction loadAction = MTLLoadActionDontCare;
- MTLStoreAction storeAction = MTLStoreActionDontCare;
- MTLLoadAction stencilLoadAction = MTLLoadActionDontCare;
- MTLStoreAction stencilStoreAction = MTLStoreActionDontCare;
- uint32_t samples = 1;
-
- /*!
- * @brief Returns true if this attachment is first used in the given subpass.
- * @param p_subpass
- * @return
- */
- _FORCE_INLINE_ bool isFirstUseOf(MDSubpass const &p_subpass) const {
- return p_subpass.subpass_index == firstUseSubpassIndex;
- }
-
- /*!
- * @brief Returns true if this attachment is last used in the given subpass.
- * @param p_subpass
- * @return
- */
- _FORCE_INLINE_ bool isLastUseOf(MDSubpass const &p_subpass) const {
- return p_subpass.subpass_index == lastUseSubpassIndex;
- }
-
- void linkToSubpass(MDRenderPass const &p_pass);
-
- MTLStoreAction getMTLStoreAction(MDSubpass const &p_subpass,
- bool p_is_rendering_entire_area,
- bool p_has_resolve,
- bool p_can_resolve,
- bool p_is_stencil) const;
- bool configureDescriptor(MTLRenderPassAttachmentDescriptor *p_desc,
- PixelFormats &p_pf,
- MDSubpass const &p_subpass,
- id<MTLTexture> p_attachment,
- bool p_is_rendering_entire_area,
- bool p_has_resolve,
- bool p_can_resolve,
- bool p_is_stencil) const;
- /** Returns whether this attachment should be cleared in the subpass. */
- bool shouldClear(MDSubpass const &p_subpass, bool p_is_stencil) const;
-};
-
-class API_AVAILABLE(macos(11.0), ios(14.0)) MDRenderPass {
-public:
- Vector<MDAttachment> attachments;
- Vector<MDSubpass> subpasses;
-
- uint32_t get_sample_count() const {
- return attachments.is_empty() ? 1 : attachments[0].samples;
- }
-
- MDRenderPass(Vector<MDAttachment> &p_attachments, Vector<MDSubpass> &p_subpasses);
-};
-
class API_AVAILABLE(macos(11.0), ios(14.0)) MDPipeline {
public:
MDPipelineType type;
@@ -892,13 +889,39 @@ public:
};
class API_AVAILABLE(macos(11.0), ios(14.0)) MDFrameBuffer {
-public:
Vector<MTL::Texture> textures;
+
+public:
Size2i size;
MDFrameBuffer(Vector<MTL::Texture> p_textures, Size2i p_size) :
textures(p_textures), size(p_size) {}
MDFrameBuffer() {}
+ /// Returns the texture at the given index.
+ _ALWAYS_INLINE_ MTL::Texture get_texture(uint32_t p_idx) const {
+ return textures[p_idx];
+ }
+
+ /// Returns true if the texture at the given index is not nil.
+ _ALWAYS_INLINE_ bool has_texture(uint32_t p_idx) const {
+ return textures[p_idx] != nil;
+ }
+
+ /// Set the texture at the given index.
+ _ALWAYS_INLINE_ void set_texture(uint32_t p_idx, MTL::Texture p_texture) {
+ textures.write[p_idx] = p_texture;
+ }
+
+ /// Unset or nil the texture at the given index.
+ _ALWAYS_INLINE_ void unset_texture(uint32_t p_idx) {
+ textures.write[p_idx] = nil;
+ }
+
+ /// Resizes buffers to the specified size.
+ _ALWAYS_INLINE_ void set_texture_count(uint32_t p_size) {
+ textures.resize(p_size);
+ }
+
virtual ~MDFrameBuffer() = default;
};
diff --git a/drivers/metal/metal_objects.mm b/drivers/metal/metal_objects.mm
index c3906af159..11ab209d60 100644
--- a/drivers/metal/metal_objects.mm
+++ b/drivers/metal/metal_objects.mm
@@ -96,6 +96,9 @@ void MDCommandBuffer::bind_pipeline(RDD::PipelineID p_pipeline) {
MDRenderPipeline *rp = (MDRenderPipeline *)p;
if (render.encoder == nil) {
+ // This error would happen if the render pass failed.
+ ERR_FAIL_NULL_MSG(render.desc, "Render pass descriptor is null.");
+
// This condition occurs when there are no attachments when calling render_next_subpass()
// and is due to the SUPPORTS_FRAGMENT_SHADER_WITH_ONLY_SIDE_EFFECTS flag.
render.desc.defaultRasterSampleCount = static_cast<NSUInteger>(rp->sample_count);
@@ -223,8 +226,9 @@ void MDCommandBuffer::render_bind_uniform_set(RDD::UniformSetID p_uniform_set, R
void MDCommandBuffer::render_clear_attachments(VectorView<RDD::AttachmentClear> p_attachment_clears, VectorView<Rect2i> p_rects) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
- uint32_t vertex_count = p_rects.size() * 6;
+ const MDSubpass &subpass = render.get_subpass();
+ uint32_t vertex_count = p_rects.size() * 6 * subpass.view_count;
simd::float4 vertices[vertex_count];
simd::float4 clear_colors[ClearAttKey::ATTACHMENT_COUNT];
@@ -235,6 +239,9 @@ void MDCommandBuffer::render_clear_attachments(VectorView<RDD::AttachmentClear>
ClearAttKey key;
key.sample_count = render.pass->get_sample_count();
+ if (subpass.view_count > 1) {
+ key.enable_layered_rendering();
+ }
float depth_value = 0;
uint32_t stencil_value = 0;
@@ -245,7 +252,7 @@ void MDCommandBuffer::render_clear_attachments(VectorView<RDD::AttachmentClear>
if (attClear.aspect.has_flag(RDD::TEXTURE_ASPECT_COLOR_BIT)) {
attachment_index = attClear.color_attachment;
} else {
- attachment_index = render.pass->subpasses[render.current_subpass].depth_stencil_reference.attachment;
+ attachment_index = subpass.depth_stencil_reference.attachment;
}
MDAttachment const &mda = render.pass->attachments[attachment_index];
@@ -310,6 +317,13 @@ void MDCommandBuffer::render_clear_attachments(VectorView<RDD::AttachmentClear>
void MDCommandBuffer::_render_set_dirty_state() {
_render_bind_uniform_sets();
+ MDSubpass const &subpass = render.get_subpass();
+ if (subpass.view_count > 1) {
+ uint32_t view_range[2] = { 0, subpass.view_count };
+ [render.encoder setVertexBytes:view_range length:sizeof(view_range) atIndex:VIEW_MASK_BUFFER_INDEX];
+ [render.encoder setFragmentBytes:view_range length:sizeof(view_range) atIndex:VIEW_MASK_BUFFER_INDEX];
+ }
+
if (render.dirty.has_flag(RenderState::DIRTY_PIPELINE)) {
[render.encoder setRenderPipelineState:render.pipeline->state];
}
@@ -492,36 +506,40 @@ uint32_t MDCommandBuffer::_populate_vertices(simd::float4 *p_vertices, uint32_t
simd::float4 vtx;
uint32_t idx = p_index;
- vtx.z = 0.0;
- vtx.w = (float)1;
+ uint32_t endLayer = render.get_subpass().view_count;
+
+ for (uint32_t layer = 0; layer < endLayer; layer++) {
+ vtx.z = 0.0;
+ vtx.w = (float)layer;
- // Top left vertex - First triangle.
- vtx.y = topPos;
- vtx.x = leftPos;
- p_vertices[idx++] = vtx;
+ // Top left vertex - First triangle.
+ vtx.y = topPos;
+ vtx.x = leftPos;
+ p_vertices[idx++] = vtx;
- // Bottom left vertex.
- vtx.y = bottomPos;
- vtx.x = leftPos;
- p_vertices[idx++] = vtx;
+ // Bottom left vertex.
+ vtx.y = bottomPos;
+ vtx.x = leftPos;
+ p_vertices[idx++] = vtx;
- // Bottom right vertex.
- vtx.y = bottomPos;
- vtx.x = rightPos;
- p_vertices[idx++] = vtx;
+ // Bottom right vertex.
+ vtx.y = bottomPos;
+ vtx.x = rightPos;
+ p_vertices[idx++] = vtx;
- // Bottom right vertex - Second triangle.
- p_vertices[idx++] = vtx;
+ // Bottom right vertex - Second triangle.
+ p_vertices[idx++] = vtx;
- // Top right vertex.
- vtx.y = topPos;
- vtx.x = rightPos;
- p_vertices[idx++] = vtx;
+ // Top right vertex.
+ vtx.y = topPos;
+ vtx.x = rightPos;
+ p_vertices[idx++] = vtx;
- // Top left vertex.
- vtx.y = topPos;
- vtx.x = leftPos;
- p_vertices[idx++] = vtx;
+ // Top left vertex.
+ vtx.y = topPos;
+ vtx.x = leftPos;
+ p_vertices[idx++] = vtx;
+ }
return idx;
}
@@ -548,8 +566,7 @@ void MDCommandBuffer::render_begin_pass(RDD::RenderPassID p_render_pass, RDD::Fr
void MDCommandBuffer::_end_render_pass() {
MDFrameBuffer const &fb_info = *render.frameBuffer;
- MDRenderPass const &pass_info = *render.pass;
- MDSubpass const &subpass = pass_info.subpasses[render.current_subpass];
+ MDSubpass const &subpass = render.get_subpass();
PixelFormats &pf = device_driver->get_pixel_formats();
@@ -557,11 +574,11 @@ void MDCommandBuffer::_end_render_pass() {
uint32_t color_index = subpass.color_references[i].attachment;
uint32_t resolve_index = subpass.resolve_references[i].attachment;
DEV_ASSERT((color_index == RDD::AttachmentReference::UNUSED) == (resolve_index == RDD::AttachmentReference::UNUSED));
- if (color_index == RDD::AttachmentReference::UNUSED || !fb_info.textures[color_index]) {
+ if (color_index == RDD::AttachmentReference::UNUSED || !fb_info.has_texture(color_index)) {
continue;
}
- id<MTLTexture> resolve_tex = fb_info.textures[resolve_index];
+ id<MTLTexture> resolve_tex = fb_info.get_texture(resolve_index);
CRASH_COND_MSG(!flags::all(pf.getCapabilities(resolve_tex.pixelFormat), kMTLFmtCapsResolve), "not implemented: unresolvable texture types");
// see: https://github.com/KhronosGroup/MoltenVK/blob/d20d13fe2735adb845636a81522df1b9d89c0fba/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm#L407
@@ -572,7 +589,7 @@ void MDCommandBuffer::_end_render_pass() {
void MDCommandBuffer::_render_clear_render_area() {
MDRenderPass const &pass = *render.pass;
- MDSubpass const &subpass = pass.subpasses[render.current_subpass];
+ MDSubpass const &subpass = render.get_subpass();
// First determine attachments that should be cleared.
LocalVector<RDD::AttachmentClear> clears;
@@ -619,9 +636,14 @@ void MDCommandBuffer::render_next_subpass() {
MDFrameBuffer const &fb = *render.frameBuffer;
MDRenderPass const &pass = *render.pass;
- MDSubpass const &subpass = pass.subpasses[render.current_subpass];
+ MDSubpass const &subpass = render.get_subpass();
MTLRenderPassDescriptor *desc = MTLRenderPassDescriptor.renderPassDescriptor;
+
+ if (subpass.view_count > 1) {
+ desc.renderTargetArrayLength = subpass.view_count;
+ }
+
PixelFormats &pf = device_driver->get_pixel_formats();
uint32_t attachmentCount = 0;
@@ -638,7 +660,7 @@ void MDCommandBuffer::render_next_subpass() {
bool has_resolve = resolveIdx != RDD::AttachmentReference::UNUSED;
bool can_resolve = true;
if (resolveIdx != RDD::AttachmentReference::UNUSED) {
- id<MTLTexture> resolve_tex = fb.textures[resolveIdx];
+ id<MTLTexture> resolve_tex = fb.get_texture(resolveIdx);
can_resolve = flags::all(pf.getCapabilities(resolve_tex.pixelFormat), kMTLFmtCapsResolve);
if (can_resolve) {
ca.resolveTexture = resolve_tex;
@@ -649,7 +671,9 @@ void MDCommandBuffer::render_next_subpass() {
MDAttachment const &attachment = pass.attachments[idx];
- id<MTLTexture> tex = fb.textures[idx];
+ id<MTLTexture> tex = fb.get_texture(idx);
+ ERR_FAIL_NULL_MSG(tex, "Frame buffer color texture is null.");
+
if ((attachment.type & MDAttachmentType::Color)) {
if (attachment.configureDescriptor(ca, pf, subpass, tex, render.is_rendering_entire_area, has_resolve, can_resolve, false)) {
Color clearColor = render.clear_values[idx].color;
@@ -662,7 +686,8 @@ void MDCommandBuffer::render_next_subpass() {
attachmentCount += 1;
uint32_t idx = subpass.depth_stencil_reference.attachment;
MDAttachment const &attachment = pass.attachments[idx];
- id<MTLTexture> tex = fb.textures[idx];
+ id<MTLTexture> tex = fb.get_texture(idx);
+ ERR_FAIL_NULL_MSG(tex, "Frame buffer depth / stencil texture is null.");
if (attachment.type & MDAttachmentType::Depth) {
MTLRenderPassDepthAttachmentDescriptor *da = desc.depthAttachment;
if (attachment.configureDescriptor(da, pf, subpass, tex, render.is_rendering_entire_area, false, false, false)) {
@@ -702,8 +727,15 @@ void MDCommandBuffer::render_draw(uint32_t p_vertex_count,
uint32_t p_base_vertex,
uint32_t p_first_instance) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");
+
_render_set_dirty_state();
+ MDSubpass const &subpass = render.get_subpass();
+ if (subpass.view_count > 1) {
+ p_instance_count *= subpass.view_count;
+ }
+
DEV_ASSERT(render.dirty == 0);
id<MTLRenderCommandEncoder> enc = render.encoder;
@@ -751,8 +783,15 @@ void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
int32_t p_vertex_offset,
uint32_t p_first_instance) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");
+
_render_set_dirty_state();
+ MDSubpass const &subpass = render.get_subpass();
+ if (subpass.view_count > 1) {
+ p_instance_count *= subpass.view_count;
+ }
+
id<MTLRenderCommandEncoder> enc = render.encoder;
uint32_t index_offset = render.index_offset;
@@ -770,6 +809,8 @@ void MDCommandBuffer::render_draw_indexed(uint32_t p_index_count,
void MDCommandBuffer::render_draw_indexed_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");
+
_render_set_dirty_state();
id<MTLRenderCommandEncoder> enc = render.encoder;
@@ -794,6 +835,8 @@ void MDCommandBuffer::render_draw_indexed_indirect_count(RDD::BufferID p_indirec
void MDCommandBuffer::render_draw_indirect(RDD::BufferID p_indirect_buffer, uint64_t p_offset, uint32_t p_draw_count, uint32_t p_stride) {
DEV_ASSERT(type == MDCommandBufferStateType::Render);
+ ERR_FAIL_NULL_MSG(render.pipeline, "No pipeline set for render command buffer.");
+
_render_set_dirty_state();
id<MTLRenderCommandEncoder> enc = render.encoder;
@@ -813,6 +856,42 @@ void MDCommandBuffer::render_draw_indirect_count(RDD::BufferID p_indirect_buffer
ERR_FAIL_MSG("not implemented");
}
+void MDCommandBuffer::render_end_pass() {
+ DEV_ASSERT(type == MDCommandBufferStateType::Render);
+
+ render.end_encoding();
+ render.reset();
+ type = MDCommandBufferStateType::None;
+}
+
+#pragma mark - RenderState
+
+void MDCommandBuffer::RenderState::reset() {
+ pass = nil;
+ frameBuffer = nil;
+ pipeline = nil;
+ current_subpass = UINT32_MAX;
+ render_area = {};
+ is_rendering_entire_area = false;
+ desc = nil;
+ encoder = nil;
+ index_buffer = nil;
+ index_type = MTLIndexTypeUInt16;
+ dirty = DIRTY_NONE;
+ uniform_sets.clear();
+ uniform_set_mask = 0;
+ clear_values.clear();
+ viewports.clear();
+ scissors.clear();
+ blend_constants.reset();
+ vertex_buffers.clear();
+ vertex_offsets.clear();
+ // Keep the keys, as they are likely to be used again.
+ for (KeyValue<StageResourceUsage, LocalVector<__unsafe_unretained id<MTLResource>>> &kv : resource_usage) {
+ kv.value.clear();
+ }
+}
+
void MDCommandBuffer::RenderState::end_encoding() {
if (encoder == nil) {
return;
@@ -842,6 +921,8 @@ void MDCommandBuffer::RenderState::end_encoding() {
encoder = nil;
}
+#pragma mark - ComputeState
+
void MDCommandBuffer::ComputeState::end_encoding() {
if (encoder == nil) {
return;
@@ -862,14 +943,6 @@ void MDCommandBuffer::ComputeState::end_encoding() {
encoder = nil;
}
-void MDCommandBuffer::render_end_pass() {
- DEV_ASSERT(type == MDCommandBufferStateType::Render);
-
- render.end_encoding();
- render.reset();
- type = MDCommandBufferStateType::None;
-}
-
#pragma mark - Compute
void MDCommandBuffer::compute_bind_uniform_set(RDD::UniformSetID p_uniform_set, RDD::ShaderID p_shader, uint32_t p_set_index) {
@@ -943,8 +1016,11 @@ void MDComputeShader::encode_push_constant_data(VectorView<uint32_t> p_data, MDC
[enc setBytes:ptr length:length atIndex:push_constants.binding];
}
-MDRenderShader::MDRenderShader(CharString p_name, Vector<UniformSet> p_sets, MDLibrary *_Nonnull p_vert, MDLibrary *_Nonnull p_frag) :
- MDShader(p_name, p_sets), vert(p_vert), frag(p_frag) {
+MDRenderShader::MDRenderShader(CharString p_name,
+ bool p_needs_view_mask_buffer,
+ Vector<UniformSet> p_sets,
+ MDLibrary *_Nonnull p_vert, MDLibrary *_Nonnull p_frag) :
+ MDShader(p_name, p_sets), needs_view_mask_buffer(p_needs_view_mask_buffer), vert(p_vert), frag(p_frag) {
}
void MDRenderShader::encode_push_constant_data(VectorView<uint32_t> p_data, MDCommandBuffer *p_cb) {
@@ -1279,7 +1355,7 @@ typedef struct {
typedef struct {
float4 v_position [[position]];
- uint layer;
+ uint layer%s;
} VaryingsPos;
vertex VaryingsPos vertClear(AttributesPos attributes [[stage_in]], constant ClearColorsIn& ccIn [[buffer(0)]]) {
@@ -1288,7 +1364,7 @@ vertex VaryingsPos vertClear(AttributesPos attributes [[stage_in]], constant Cle
varyings.layer = uint(attributes.a_position.w);
return varyings;
}
-)", ClearAttKey::DEPTH_INDEX];
+)", p_key.is_layered_rendering_enabled() ? " [[render_target_array_index]]" : "", ClearAttKey::DEPTH_INDEX];
return new_func(msl, @"vertClear", nil);
}
@@ -1578,7 +1654,7 @@ void ShaderCacheEntry::notify_free() const {
self->_library = library;
self->_error = error;
if (error) {
- ERR_PRINT(String(U"Error compiling shader %s: %s").format(entry->name.get_data(), error.localizedDescription.UTF8String));
+ ERR_PRINT(vformat(U"Error compiling shader %s: %s", entry->name.get_data(), error.localizedDescription.UTF8String));
}
{
diff --git a/drivers/metal/rendering_context_driver_metal.mm b/drivers/metal/rendering_context_driver_metal.mm
index b97b586352..cf8c7e1c83 100644
--- a/drivers/metal/rendering_context_driver_metal.mm
+++ b/drivers/metal/rendering_context_driver_metal.mm
@@ -134,7 +134,7 @@ public:
frame_buffers.resize(p_desired_framebuffer_count);
for (uint32_t i = 0; i < p_desired_framebuffer_count; i++) {
// Reserve space for the drawable texture.
- frame_buffers[i].textures.resize(1);
+ frame_buffers[i].set_texture_count(1);
}
return OK;
@@ -154,7 +154,7 @@ public:
id<CAMetalDrawable> drawable = layer.nextDrawable;
ERR_FAIL_NULL_V_MSG(drawable, RDD::FramebufferID(), "no drawable available");
drawables[rear] = drawable;
- frame_buffer.textures.write[0] = drawable.texture;
+ frame_buffer.set_texture(0, drawable.texture);
return RDD::FramebufferID(&frame_buffer);
}
@@ -165,7 +165,7 @@ public:
}
// Release texture and drawable.
- frame_buffers[front].textures.write[0] = nil;
+ frame_buffers[front].unset_texture(0);
id<MTLDrawable> drawable = drawables[front];
drawables[front] = nil;
diff --git a/drivers/metal/rendering_device_driver_metal.h b/drivers/metal/rendering_device_driver_metal.h
index f62a164ef9..e238de958e 100644
--- a/drivers/metal/rendering_device_driver_metal.h
+++ b/drivers/metal/rendering_device_driver_metal.h
@@ -239,7 +239,13 @@ private:
friend struct PushConstantData;
private:
- Error _reflect_spirv16(VectorView<ShaderStageSPIRVData> p_spirv, ShaderReflection &r_reflection);
+ /// Contains additional metadata about the shader.
+ struct ShaderMeta {
+ /// Indicates whether the shader uses multiview.
+ bool has_multiview = false;
+ };
+
+ Error _reflect_spirv16(VectorView<ShaderStageSPIRVData> p_spirv, ShaderReflection &r_reflection, ShaderMeta &r_shader_meta);
public:
virtual String shader_get_binary_cache_key() override final;
diff --git a/drivers/metal/rendering_device_driver_metal.mm b/drivers/metal/rendering_device_driver_metal.mm
index 4da11ecd21..d90f528a14 100644
--- a/drivers/metal/rendering_device_driver_metal.mm
+++ b/drivers/metal/rendering_device_driver_metal.mm
@@ -1026,7 +1026,7 @@ void RenderingDeviceDriverMetal::framebuffer_free(FramebufferID p_framebuffer) {
#pragma mark - Shader
-const uint32_t SHADER_BINARY_VERSION = 1;
+const uint32_t SHADER_BINARY_VERSION = 2;
// region Serialization
@@ -1503,6 +1503,7 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) ShaderBinaryData {
uint32_t fragment_output_mask = UINT32_MAX;
uint32_t spirv_specialization_constants_ids_mask = UINT32_MAX;
uint32_t is_compute = UINT32_MAX;
+ uint32_t needs_view_mask_buffer = UINT32_MAX;
ComputeSize compute_local_size;
PushConstantData push_constant;
LocalVector<ShaderStageData> stages;
@@ -1523,6 +1524,7 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) ShaderBinaryData {
size += sizeof(uint32_t); // fragment_output_mask
size += sizeof(uint32_t); // spirv_specialization_constants_ids_mask
size += sizeof(uint32_t); // is_compute
+ size += sizeof(uint32_t); // needs_view_mask_buffer
size += compute_local_size.serialize_size(); // compute_local_size
size += push_constant.serialize_size(); // push_constant
size += sizeof(uint32_t); // stages.size()
@@ -1547,6 +1549,7 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) ShaderBinaryData {
p_writer.write(fragment_output_mask);
p_writer.write(spirv_specialization_constants_ids_mask);
p_writer.write(is_compute);
+ p_writer.write(needs_view_mask_buffer);
p_writer.write(compute_local_size);
p_writer.write(push_constant);
p_writer.write(VectorView(stages));
@@ -1561,6 +1564,7 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) ShaderBinaryData {
p_reader.read(fragment_output_mask);
p_reader.read(spirv_specialization_constants_ids_mask);
p_reader.read(is_compute);
+ p_reader.read(needs_view_mask_buffer);
p_reader.read(compute_local_size);
p_reader.read(push_constant);
p_reader.read(stages);
@@ -1572,14 +1576,16 @@ struct API_AVAILABLE(macos(11.0), ios(14.0)) ShaderBinaryData {
// endregion
String RenderingDeviceDriverMetal::shader_get_binary_cache_key() {
- return "Metal-SV" + uitos(SHADER_BINARY_VERSION);
+ static const String cache_key = "Metal-SV" + uitos(SHADER_BINARY_VERSION);
+ return cache_key;
}
-Error RenderingDeviceDriverMetal::_reflect_spirv16(VectorView<ShaderStageSPIRVData> p_spirv, ShaderReflection &r_reflection) {
+Error RenderingDeviceDriverMetal::_reflect_spirv16(VectorView<ShaderStageSPIRVData> p_spirv, ShaderReflection &r_reflection, ShaderMeta &r_shader_meta) {
using namespace spirv_cross;
using spirv_cross::Resource;
r_reflection = {};
+ r_shader_meta = {};
for (uint32_t i = 0; i < p_spirv.size(); i++) {
ShaderStageSPIRVData const &v = p_spirv[i];
@@ -1811,6 +1817,20 @@ Error RenderingDeviceDriverMetal::_reflect_spirv16(VectorView<ShaderStageSPIRVDa
}
}
+ for (const BuiltInResource &res : resources.builtin_inputs) {
+ if (res.builtin == spv::BuiltInViewIndex || res.builtin == spv::BuiltInViewportIndex) {
+ r_shader_meta.has_multiview = true;
+ }
+ }
+
+ if (!r_shader_meta.has_multiview) {
+ for (const BuiltInResource &res : resources.builtin_outputs) {
+ if (res.builtin == spv::BuiltInViewIndex || res.builtin == spv::BuiltInViewportIndex) {
+ r_shader_meta.has_multiview = true;
+ }
+ }
+ }
+
// Specialization constants.
for (SpecializationConstant const &constant : compiler.get_specialization_constants()) {
int32_t existing = -1;
@@ -1874,7 +1894,8 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
using spirv_cross::Resource;
ShaderReflection spirv_data;
- ERR_FAIL_COND_V(_reflect_spirv16(p_spirv, spirv_data), Result());
+ ShaderMeta shader_meta;
+ ERR_FAIL_COND_V(_reflect_spirv16(p_spirv, spirv_data, shader_meta), Result());
ShaderBinaryData bin_data{};
if (!p_shader_name.is_empty()) {
@@ -1893,6 +1914,7 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
bin_data.is_compute = spirv_data.is_compute;
bin_data.push_constant.size = spirv_data.push_constant_size;
bin_data.push_constant.stages = (ShaderStageUsage)(uint8_t)spirv_data.push_constant_stages;
+ bin_data.needs_view_mask_buffer = shader_meta.has_multiview ? 1 : 0;
for (uint32_t i = 0; i < spirv_data.uniform_sets.size(); i++) {
const ::Vector<ShaderUniform> &spirv_set = spirv_data.uniform_sets[i];
@@ -1947,6 +1969,11 @@ Vector<uint8_t> RenderingDeviceDriverMetal::shader_compile_binary_from_spirv(Vec
msl_options.pad_fragment_output_components = true;
msl_options.r32ui_alignment_constant_id = R32UI_ALIGNMENT_CONSTANT_ID;
msl_options.agx_manual_cube_grad_fixup = true;
+ if (shader_meta.has_multiview) {
+ msl_options.multiview = true;
+ msl_options.multiview_layered_rendering = true;
+ msl_options.view_mask_buffer_index = VIEW_MASK_BUFFER_INDEX;
+ }
CompilerGLSL::Options options{};
options.vertex.flip_vert_y = true;
@@ -2448,7 +2475,7 @@ RDD::ShaderID RenderingDeviceDriverMetal::shader_create_from_bytecode(const Vect
#endif
shader = cs;
} else {
- MDRenderShader *rs = new MDRenderShader(binary_data.shader_name, uniform_sets, libraries[ShaderStage::SHADER_STAGE_VERTEX], libraries[ShaderStage::SHADER_STAGE_FRAGMENT]);
+ MDRenderShader *rs = new MDRenderShader(binary_data.shader_name, (bool)binary_data.needs_view_mask_buffer, uniform_sets, libraries[ShaderStage::SHADER_STAGE_VERTEX], libraries[ShaderStage::SHADER_STAGE_FRAGMENT]);
uint32_t *vert_binding = binary_data.push_constant.msl_binding.getptr(SHADER_STAGE_VERTEX);
if (vert_binding) {
@@ -2956,6 +2983,7 @@ RDD::RenderPassID RenderingDeviceDriverMetal::render_pass_create(VectorView<Atta
for (uint32_t i = 0; i < subpass_count; i++) {
MDSubpass &subpass = subpasses.write[i];
subpass.subpass_index = i;
+ subpass.view_count = p_view_count;
subpass.input_references = p_subpasses[i].input_references;
subpass.color_references = p_subpasses[i].color_references;
subpass.depth_stencil_reference = p_subpasses[i].depth_stencil_reference;
@@ -3675,8 +3703,7 @@ void RenderingDeviceDriverMetal::set_object_name(ObjectType p_type, ID p_driver_
uint64_t RenderingDeviceDriverMetal::get_resource_native_handle(DriverResource p_type, ID p_driver_id) {
switch (p_type) {
case DRIVER_RESOURCE_LOGICAL_DEVICE: {
- uintptr_t devicePtr = (uintptr_t)(__bridge void *)device;
- return (uint64_t)devicePtr;
+ return (uint64_t)(uintptr_t)(__bridge void *)device;
}
case DRIVER_RESOURCE_PHYSICAL_DEVICE: {
return 0;
@@ -3685,7 +3712,7 @@ uint64_t RenderingDeviceDriverMetal::get_resource_native_handle(DriverResource p
return 0;
}
case DRIVER_RESOURCE_COMMAND_QUEUE: {
- return 0;
+ return (uint64_t)(uintptr_t)(__bridge void *)device_queue;
}
case DRIVER_RESOURCE_QUEUE_FAMILY: {
return 0;
@@ -3702,15 +3729,20 @@ uint64_t RenderingDeviceDriverMetal::get_resource_native_handle(DriverResource p
case DRIVER_RESOURCE_SAMPLER: {
return p_driver_id.id;
}
- case DRIVER_RESOURCE_UNIFORM_SET:
+ case DRIVER_RESOURCE_UNIFORM_SET: {
return 0;
+ }
case DRIVER_RESOURCE_BUFFER: {
return p_driver_id.id;
}
- case DRIVER_RESOURCE_COMPUTE_PIPELINE:
- return 0;
- case DRIVER_RESOURCE_RENDER_PIPELINE:
- return 0;
+ case DRIVER_RESOURCE_COMPUTE_PIPELINE: {
+ MDComputePipeline *pipeline = (MDComputePipeline *)(p_driver_id.id);
+ return (uint64_t)(uintptr_t)(__bridge void *)pipeline->state;
+ }
+ case DRIVER_RESOURCE_RENDER_PIPELINE: {
+ MDRenderPipeline *pipeline = (MDRenderPipeline *)(p_driver_id.id);
+ return (uint64_t)(uintptr_t)(__bridge void *)pipeline->state;
+ }
default: {
return 0;
}
@@ -3842,7 +3874,7 @@ uint64_t RenderingDeviceDriverMetal::api_trait_get(ApiTrait p_trait) {
bool RenderingDeviceDriverMetal::has_feature(Features p_feature) {
switch (p_feature) {
case SUPPORTS_MULTIVIEW:
- return false;
+ return multiview_capabilities.is_supported;
case SUPPORTS_FSR_HALF_FLOAT:
return true;
case SUPPORTS_ATTACHMENT_VRS:
@@ -3951,6 +3983,18 @@ Error RenderingDeviceDriverMetal::initialize(uint32_t p_device_index, uint32_t p
metal_device_properties = memnew(MetalDeviceProperties(device));
pixel_formats = memnew(PixelFormats(device));
+ if (metal_device_properties->features.layeredRendering) {
+ multiview_capabilities.is_supported = true;
+ multiview_capabilities.max_view_count = metal_device_properties->limits.maxViewports;
+ // NOTE: I'm not sure what the limit is as I don't see it referenced anywhere
+ multiview_capabilities.max_instance_count = UINT32_MAX;
+
+ print_verbose("- Metal multiview supported:");
+ print_verbose(" max view count: " + itos(multiview_capabilities.max_view_count));
+ print_verbose(" max instances: " + itos(multiview_capabilities.max_instance_count));
+ } else {
+ print_verbose("- Metal multiview not supported");
+ }
// Check required features and abort if any of them is missing.
if (!metal_device_properties->features.imageCubeArray) {
diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp
index 3d584341ed..43ad0799ba 100644
--- a/drivers/unix/file_access_unix.cpp
+++ b/drivers/unix/file_access_unix.cpp
@@ -41,6 +41,11 @@
#include <sys/types.h>
#include <unistd.h>
+#if defined(TOOLS_ENABLED)
+#include <limits.h>
+#include <stdlib.h>
+#endif
+
void FileAccessUnix::check_errors() const {
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
@@ -87,6 +92,22 @@ Error FileAccessUnix::open_internal(const String &p_path, int p_mode_flags) {
}
}
+#if defined(TOOLS_ENABLED)
+ if (p_mode_flags & READ) {
+ String real_path = get_real_path();
+ if (real_path != path) {
+ // Don't warn on symlinks, since they can be used to simply share addons on multiple projects.
+ if (real_path.to_lower() == path.to_lower()) {
+ // The File system is case insensitive, but other platforms can be sensitive to it
+ // To ease cross-platform development, we issue a warning if users try to access
+ // a file using the wrong case (which *works* on Windows and macOS, but won't on other
+ // platforms).
+ WARN_PRINT(vformat("Case mismatch opening requested file '%s', stored as '%s' in the filesystem. This file will not open when exported to other case-sensitive platforms.", path, real_path));
+ }
+ }
+ }
+#endif
+
if (is_backup_save_enabled() && (p_mode_flags == WRITE)) {
save_path = path;
// Create a temporary file in the same directory as the target file.
@@ -173,6 +194,26 @@ String FileAccessUnix::get_path_absolute() const {
return path;
}
+#if defined(TOOLS_ENABLED)
+String FileAccessUnix::get_real_path() const {
+ char *resolved_path = ::realpath(path.utf8().get_data(), nullptr);
+
+ if (!resolved_path) {
+ return path;
+ }
+
+ String result;
+ Error parse_ok = result.parse_utf8(resolved_path);
+ ::free(resolved_path);
+
+ if (parse_ok != OK) {
+ return path;
+ }
+
+ return result.simplify_path();
+}
+#endif
+
void FileAccessUnix::seek(uint64_t p_position) {
ERR_FAIL_NULL_MSG(f, "File must be opened before use.");
diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h
index 76f629f7c2..7caf8a14d7 100644
--- a/drivers/unix/file_access_unix.h
+++ b/drivers/unix/file_access_unix.h
@@ -51,6 +51,10 @@ class FileAccessUnix : public FileAccess {
void _close();
+#if defined(TOOLS_ENABLED)
+ String get_real_path() const; // Returns the resolved real path for the current open file.
+#endif
+
public:
static CloseNotificationFunc close_notification_func;
diff --git a/drivers/unix/net_socket_posix.cpp b/drivers/unix/net_socket_unix.cpp
index 5caa33100e..3be615d9ad 100644
--- a/drivers/unix/net_socket_posix.cpp
+++ b/drivers/unix/net_socket_unix.cpp
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* net_socket_posix.cpp */
+/* net_socket_unix.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,13 +28,11 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#include "net_socket_posix.h"
-
// Some proprietary Unix-derived platforms don't expose Unix sockets
// so this allows skipping this file to reimplement this API differently.
-#ifndef UNIX_SOCKET_UNAVAILABLE
+#if defined(UNIX_ENABLED) && !defined(UNIX_SOCKET_UNAVAILABLE)
-#if defined(UNIX_ENABLED)
+#include "net_socket_unix.h"
#include <errno.h>
#include <fcntl.h>
@@ -62,44 +60,11 @@
#define IPV6_DROP_MEMBERSHIP IPV6_LEAVE_GROUP
#endif
-// Some custom defines to minimize ifdefs
-#define SOCK_EMPTY -1
-#define SOCK_BUF(x) x
-#define SOCK_CBUF(x) x
-#define SOCK_IOCTL ioctl
-#define SOCK_FIONREAD_LEN_TYPE int
-#define SOCK_CLOSE ::close
-#define SOCK_CONNECT(p_sock, p_addr, p_addr_len) ::connect(p_sock, p_addr, p_addr_len)
-
-/* Windows */
-#elif defined(WINDOWS_ENABLED)
-#include <winsock2.h>
-#include <ws2tcpip.h>
-
-#include <mswsock.h>
-// Some custom defines to minimize ifdefs
-#define SOCK_EMPTY INVALID_SOCKET
-#define SOCK_BUF(x) (char *)(x)
-#define SOCK_CBUF(x) (const char *)(x)
-#define SOCK_IOCTL ioctlsocket
-#define SOCK_FIONREAD_LEN_TYPE unsigned long
-#define SOCK_CLOSE closesocket
-// connect is broken on windows under certain conditions, reasons unknown:
-// See https://github.com/godotengine/webrtc-native/issues/6
-#define SOCK_CONNECT(p_sock, p_addr, p_addr_len) ::WSAConnect(p_sock, p_addr, p_addr_len, nullptr, nullptr, nullptr, nullptr)
-
-// Workaround missing flag in MinGW
-#if defined(__MINGW32__) && !defined(SIO_UDP_NETRESET)
-#define SIO_UDP_NETRESET _WSAIOW(IOC_VENDOR, 15)
-#endif
-
-#endif // UNIX_ENABLED
-
size_t NetSocketPosix::_set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type) {
memset(p_addr, 0, sizeof(struct sockaddr_storage));
- if (p_ip_type == IP::TYPE_IPV6 || p_ip_type == IP::TYPE_ANY) { // IPv6 socket
+ if (p_ip_type == IP::TYPE_IPV6 || p_ip_type == IP::TYPE_ANY) { // IPv6 socket.
- // IPv6 only socket with IPv4 address
+ // IPv6 only socket with IPv4 address.
ERR_FAIL_COND_V(!p_ip.is_wildcard() && p_ip_type == IP::TYPE_IPV6 && p_ip.is_ipv4(), 0);
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)p_addr;
@@ -111,14 +76,14 @@ size_t NetSocketPosix::_set_addr_storage(struct sockaddr_storage *p_addr, const
addr6->sin6_addr = in6addr_any;
}
return sizeof(sockaddr_in6);
- } else { // IPv4 socket
+ } else { // IPv4 socket.
- // IPv4 socket with IPv6 address
+ // IPv4 socket with IPv6 address.
ERR_FAIL_COND_V(!p_ip.is_wildcard() && !p_ip.is_ipv4(), 0);
struct sockaddr_in *addr4 = (struct sockaddr_in *)p_addr;
addr4->sin_family = AF_INET;
- addr4->sin_port = htons(p_port); // short, network byte order
+ addr4->sin_port = htons(p_port); // Short, network byte order.
if (p_ip.is_valid()) {
memcpy(&addr4->sin_addr.s_addr, p_ip.get_ipv4(), 4);
@@ -155,26 +120,13 @@ NetSocket *NetSocketPosix::_create_func() {
}
void NetSocketPosix::make_default() {
-#if defined(WINDOWS_ENABLED)
- if (_create == nullptr) {
- WSADATA data;
- WSAStartup(MAKEWORD(2, 2), &data);
- }
-#endif
_create = _create_func;
}
void NetSocketPosix::cleanup() {
-#if defined(WINDOWS_ENABLED)
- if (_create != nullptr) {
- WSACleanup();
- }
- _create = nullptr;
-#endif
}
-NetSocketPosix::NetSocketPosix() :
- _sock(SOCK_EMPTY) {
+NetSocketPosix::NetSocketPosix() {
}
NetSocketPosix::~NetSocketPosix() {
@@ -189,29 +141,6 @@ NetSocketPosix::~NetSocketPosix() {
#endif
NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const {
-#if defined(WINDOWS_ENABLED)
- int err = WSAGetLastError();
- if (err == WSAEISCONN) {
- return ERR_NET_IS_CONNECTED;
- }
- if (err == WSAEINPROGRESS || err == WSAEALREADY) {
- return ERR_NET_IN_PROGRESS;
- }
- if (err == WSAEWOULDBLOCK) {
- return ERR_NET_WOULD_BLOCK;
- }
- if (err == WSAEADDRINUSE || err == WSAEADDRNOTAVAIL) {
- return ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE;
- }
- if (err == WSAEACCES) {
- return ERR_NET_UNAUTHORIZED;
- }
- if (err == WSAEMSGSIZE || err == WSAENOBUFS) {
- return ERR_NET_BUFFER_TOO_SMALL;
- }
- print_verbose("Socket error: " + itos(err));
- return ERR_NET_OTHER;
-#else
if (errno == EISCONN) {
return ERR_NET_IS_CONNECTED;
}
@@ -230,9 +159,8 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const {
if (errno == ENOBUFS) {
return ERR_NET_BUFFER_TOO_SMALL;
}
- print_verbose("Socket error: " + itos(errno));
+ print_verbose("Socket error: " + itos(errno) + ".");
return ERR_NET_OTHER;
-#endif
}
#if defined(__GNUC__) && !defined(__clang__)
@@ -254,7 +182,7 @@ _FORCE_INLINE_ Error NetSocketPosix::_change_multicast_group(IPAddress p_ip, Str
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V(!_can_use_ip(p_ip, false), ERR_INVALID_PARAMETER);
- // Need to force level and af_family to IP(v4) when using dual stacking and provided multicast group is IPv4
+ // Need to force level and af_family to IP(v4) when using dual stacking and provided multicast group is IPv4.
IP::Type type = _ip_type == IP::TYPE_ANY && p_ip.is_ipv4() ? IP::TYPE_IPV4 : _ip_type;
// This needs to be the proper level for the multicast group, no matter if the socket is dual stacking.
int level = type == IP::TYPE_IPV4 ? IPPROTO_IP : IPPROTO_IPV6;
@@ -277,7 +205,7 @@ _FORCE_INLINE_ Error NetSocketPosix::_change_multicast_group(IPAddress p_ip, Str
for (const IPAddress &F : c.ip_addresses) {
if (!F.is_ipv4()) {
- continue; // Wrong IP type
+ continue; // Wrong IP type.
}
if_ip = F;
break;
@@ -304,7 +232,7 @@ _FORCE_INLINE_ Error NetSocketPosix::_change_multicast_group(IPAddress p_ip, Str
return OK;
}
-void NetSocketPosix::_set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_is_stream) {
+void NetSocketPosix::_set_socket(int p_sock, IP::Type p_ip_type, bool p_is_stream) {
_sock = p_sock;
_ip_type = p_ip_type;
_is_stream = p_is_stream;
@@ -313,11 +241,9 @@ void NetSocketPosix::_set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_
}
void NetSocketPosix::_set_close_exec_enabled(bool p_enabled) {
-#ifndef WINDOWS_ENABLED
// Enable close on exec to avoid sharing with subprocesses. Off by default on Windows.
int opts = fcntl(_sock, F_GETFD);
fcntl(_sock, F_SETFD, opts | FD_CLOEXEC);
-#endif
}
Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) {
@@ -336,7 +262,7 @@ Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) {
int type = p_sock_type == TYPE_TCP ? SOCK_STREAM : SOCK_DGRAM;
_sock = socket(family, type, protocol);
- if (_sock == SOCK_EMPTY && ip_type == IP::TYPE_ANY) {
+ if (_sock == -1 && ip_type == IP::TYPE_ANY) {
// Careful here, changing the referenced parameter so the caller knows that we are using an IPv4 socket
// in place of a dual stack one, and further calls to _set_sock_addr will work as expected.
ip_type = IP::TYPE_IPV4;
@@ -344,11 +270,11 @@ Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) {
_sock = socket(family, type, protocol);
}
- ERR_FAIL_COND_V(_sock == SOCK_EMPTY, FAILED);
+ ERR_FAIL_COND_V(_sock == -1, FAILED);
_ip_type = ip_type;
if (family == AF_INET6) {
- // Select IPv4 over IPv6 mapping
+ // Select IPv4 over IPv6 mapping.
set_ipv6_only_enabled(ip_type != IP::TYPE_ANY);
}
@@ -363,36 +289,22 @@ Error NetSocketPosix::open(Type p_sock_type, IP::Type &ip_type) {
// Disable descriptor sharing with subprocesses.
_set_close_exec_enabled(true);
-#if defined(WINDOWS_ENABLED)
- if (!_is_stream) {
- // Disable windows feature/bug reporting WSAECONNRESET/WSAENETRESET when
- // recv/recvfrom and an ICMP reply was received from a previous send/sendto.
- unsigned long disable = 0;
- if (ioctlsocket(_sock, SIO_UDP_CONNRESET, &disable) == SOCKET_ERROR) {
- print_verbose("Unable to turn off UDP WSAECONNRESET behavior on Windows");
- }
- if (ioctlsocket(_sock, SIO_UDP_NETRESET, &disable) == SOCKET_ERROR) {
- // This feature seems not to be supported on wine.
- print_verbose("Unable to turn off UDP WSAENETRESET behavior on Windows");
- }
- }
-#endif
#if defined(SO_NOSIGPIPE)
- // Disable SIGPIPE (should only be relevant to stream sockets, but seems to affect UDP too on iOS)
+ // Disable SIGPIPE (should only be relevant to stream sockets, but seems to affect UDP too on iOS).
int par = 1;
- if (setsockopt(_sock, SOL_SOCKET, SO_NOSIGPIPE, SOCK_CBUF(&par), sizeof(int)) != 0) {
- print_verbose("Unable to turn off SIGPIPE on socket");
+ if (setsockopt(_sock, SOL_SOCKET, SO_NOSIGPIPE, &par, sizeof(int)) != 0) {
+ print_verbose("Unable to turn off SIGPIPE on socket.");
}
#endif
return OK;
}
void NetSocketPosix::close() {
- if (_sock != SOCK_EMPTY) {
- SOCK_CLOSE(_sock);
+ if (_sock != -1) {
+ ::close(_sock);
}
- _sock = SOCK_EMPTY;
+ _sock = -1;
_ip_type = IP::TYPE_NONE;
_is_stream = false;
}
@@ -406,7 +318,7 @@ Error NetSocketPosix::bind(IPAddress p_addr, uint16_t p_port) {
if (::bind(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
NetError err = _get_socket_error();
- print_verbose("Failed to bind socket. Error: " + itos(err));
+ print_verbose("Failed to bind socket. Error: " + itos(err) + ".");
close();
return ERR_UNAVAILABLE;
}
@@ -434,19 +346,19 @@ Error NetSocketPosix::connect_to_host(IPAddress p_host, uint16_t p_port) {
struct sockaddr_storage addr;
size_t addr_size = _set_addr_storage(&addr, p_host, p_port, _ip_type);
- if (SOCK_CONNECT(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
+ if (::connect(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
NetError err = _get_socket_error();
switch (err) {
- // We are already connected
+ // We are already connected.
case ERR_NET_IS_CONNECTED:
return OK;
- // Still waiting to connect, try again in a while
+ // Still waiting to connect, try again in a while.
case ERR_NET_WOULD_BLOCK:
case ERR_NET_IN_PROGRESS:
return ERR_BUSY;
default:
- print_verbose("Connection to remote host failed!");
+ print_verbose("Connection to remote host failed.");
close();
return FAILED;
}
@@ -458,63 +370,6 @@ Error NetSocketPosix::connect_to_host(IPAddress p_host, uint16_t p_port) {
Error NetSocketPosix::poll(PollType p_type, int p_timeout) const {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
-#if defined(WINDOWS_ENABLED)
- bool ready = false;
- fd_set rd, wr, ex;
- fd_set *rdp = nullptr;
- fd_set *wrp = nullptr;
- FD_ZERO(&rd);
- FD_ZERO(&wr);
- FD_ZERO(&ex);
- FD_SET(_sock, &ex);
- struct timeval timeout = { p_timeout / 1000, (p_timeout % 1000) * 1000 };
- // For blocking operation, pass nullptr timeout pointer to select.
- struct timeval *tp = nullptr;
- if (p_timeout >= 0) {
- // If timeout is non-negative, we want to specify the timeout instead.
- tp = &timeout;
- }
-
- switch (p_type) {
- case POLL_TYPE_IN:
- FD_SET(_sock, &rd);
- rdp = &rd;
- break;
- case POLL_TYPE_OUT:
- FD_SET(_sock, &wr);
- wrp = &wr;
- break;
- case POLL_TYPE_IN_OUT:
- FD_SET(_sock, &rd);
- FD_SET(_sock, &wr);
- rdp = &rd;
- wrp = &wr;
- }
- int ret = select(1, rdp, wrp, &ex, tp);
-
- if (ret == SOCKET_ERROR) {
- return FAILED;
- }
-
- if (ret == 0) {
- return ERR_BUSY;
- }
-
- if (FD_ISSET(_sock, &ex)) {
- _get_socket_error();
- print_verbose("Exception when polling socket.");
- return FAILED;
- }
-
- if (rdp && FD_ISSET(_sock, rdp)) {
- ready = true;
- }
- if (wrp && FD_ISSET(_sock, wrp)) {
- ready = true;
- }
-
- return ready ? OK : ERR_BUSY;
-#else
struct pollfd pfd;
pfd.fd = _sock;
pfd.events = POLLIN;
@@ -544,13 +399,12 @@ Error NetSocketPosix::poll(PollType p_type, int p_timeout) const {
}
return OK;
-#endif
}
Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) {
ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
- r_read = ::recv(_sock, SOCK_BUF(p_buffer), p_len, 0);
+ r_read = ::recv(_sock, p_buffer, p_len, 0);
if (r_read < 0) {
NetError err = _get_socket_error();
@@ -575,7 +429,7 @@ Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddr
socklen_t len = sizeof(struct sockaddr_storage);
memset(&from, 0, len);
- r_read = ::recvfrom(_sock, SOCK_BUF(p_buffer), p_len, p_peek ? MSG_PEEK : 0, (struct sockaddr *)&from, &len);
+ r_read = ::recvfrom(_sock, p_buffer, p_len, p_peek ? MSG_PEEK : 0, (struct sockaddr *)&from, &len);
if (r_read < 0) {
NetError err = _get_socket_error();
@@ -615,7 +469,7 @@ Error NetSocketPosix::send(const uint8_t *p_buffer, int p_len, int &r_sent) {
flags = MSG_NOSIGNAL;
}
#endif
- r_sent = ::send(_sock, SOCK_CBUF(p_buffer), p_len, flags);
+ r_sent = ::send(_sock, p_buffer, p_len, flags);
if (r_sent < 0) {
NetError err = _get_socket_error();
@@ -637,7 +491,7 @@ Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP
struct sockaddr_storage addr;
size_t addr_size = _set_addr_storage(&addr, p_ip, p_port, _ip_type);
- r_sent = ::sendto(_sock, SOCK_CBUF(p_buffer), p_len, 0, (struct sockaddr *)&addr, addr_size);
+ r_sent = ::sendto(_sock, p_buffer, p_len, 0, (struct sockaddr *)&addr, addr_size);
if (r_sent < 0) {
NetError err = _get_socket_error();
@@ -662,8 +516,8 @@ Error NetSocketPosix::set_broadcasting_enabled(bool p_enabled) {
}
int par = p_enabled ? 1 : 0;
- if (setsockopt(_sock, SOL_SOCKET, SO_BROADCAST, SOCK_CBUF(&par), sizeof(int)) != 0) {
- WARN_PRINT("Unable to change broadcast setting");
+ if (setsockopt(_sock, SOL_SOCKET, SO_BROADCAST, &par, sizeof(int)) != 0) {
+ WARN_PRINT("Unable to change broadcast setting.");
return FAILED;
}
return OK;
@@ -673,20 +527,15 @@ void NetSocketPosix::set_blocking_enabled(bool p_enabled) {
ERR_FAIL_COND(!is_open());
int ret = 0;
-#if defined(WINDOWS_ENABLED)
- unsigned long par = p_enabled ? 0 : 1;
- ret = SOCK_IOCTL(_sock, FIONBIO, &par);
-#else
int opts = fcntl(_sock, F_GETFL);
if (p_enabled) {
ret = fcntl(_sock, F_SETFL, opts & ~O_NONBLOCK);
} else {
ret = fcntl(_sock, F_SETFL, opts | O_NONBLOCK);
}
-#endif
if (ret != 0) {
- WARN_PRINT("Unable to change non-block mode");
+ WARN_PRINT("Unable to change non-block mode.");
}
}
@@ -696,56 +545,39 @@ void NetSocketPosix::set_ipv6_only_enabled(bool p_enabled) {
ERR_FAIL_COND(_ip_type == IP::TYPE_IPV4);
int par = p_enabled ? 1 : 0;
- if (setsockopt(_sock, IPPROTO_IPV6, IPV6_V6ONLY, SOCK_CBUF(&par), sizeof(int)) != 0) {
- WARN_PRINT("Unable to change IPv4 address mapping over IPv6 option");
+ if (setsockopt(_sock, IPPROTO_IPV6, IPV6_V6ONLY, &par, sizeof(int)) != 0) {
+ WARN_PRINT("Unable to change IPv4 address mapping over IPv6 option.");
}
}
void NetSocketPosix::set_tcp_no_delay_enabled(bool p_enabled) {
ERR_FAIL_COND(!is_open());
- ERR_FAIL_COND(!_is_stream); // Not TCP
+ ERR_FAIL_COND(!_is_stream); // Not TCP.
int par = p_enabled ? 1 : 0;
- if (setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY, SOCK_CBUF(&par), sizeof(int)) < 0) {
- ERR_PRINT("Unable to set TCP no delay option");
+ if (setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY, &par, sizeof(int)) < 0) {
+ WARN_PRINT("Unable to set TCP no delay option.");
}
}
void NetSocketPosix::set_reuse_address_enabled(bool p_enabled) {
ERR_FAIL_COND(!is_open());
-// On Windows, enabling SO_REUSEADDR actually would also enable reuse port, very bad on TCP. Denying...
-// Windows does not have this option, SO_REUSEADDR in this magical world means SO_REUSEPORT
-#ifndef WINDOWS_ENABLED
- int par = p_enabled ? 1 : 0;
- if (setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR, SOCK_CBUF(&par), sizeof(int)) < 0) {
- WARN_PRINT("Unable to set socket REUSEADDR option!");
- }
-#endif
-}
-
-void NetSocketPosix::set_reuse_port_enabled(bool p_enabled) {
- ERR_FAIL_COND(!is_open());
-
-// See comment above...
-#ifdef WINDOWS_ENABLED
-#define SO_REUSEPORT SO_REUSEADDR
-#endif
int par = p_enabled ? 1 : 0;
- if (setsockopt(_sock, SOL_SOCKET, SO_REUSEPORT, SOCK_CBUF(&par), sizeof(int)) < 0) {
- WARN_PRINT("Unable to set socket REUSEPORT option!");
+ if (setsockopt(_sock, SOL_SOCKET, SO_REUSEADDR, &par, sizeof(int)) < 0) {
+ WARN_PRINT("Unable to set socket REUSEADDR option.");
}
}
bool NetSocketPosix::is_open() const {
- return _sock != SOCK_EMPTY;
+ return _sock != -1;
}
int NetSocketPosix::get_available_bytes() const {
ERR_FAIL_COND_V(!is_open(), -1);
- SOCK_FIONREAD_LEN_TYPE len;
- int ret = SOCK_IOCTL(_sock, FIONREAD, &len);
+ int len;
+ int ret = ioctl(_sock, FIONREAD, &len);
if (ret == -1) {
_get_socket_error();
print_verbose("Error when checking available bytes on socket.");
@@ -774,8 +606,8 @@ Ref<NetSocket> NetSocketPosix::accept(IPAddress &r_ip, uint16_t &r_port) {
struct sockaddr_storage their_addr;
socklen_t size = sizeof(their_addr);
- SOCKET_TYPE fd = ::accept(_sock, (struct sockaddr *)&their_addr, &size);
- if (fd == SOCK_EMPTY) {
+ int fd = ::accept(_sock, (struct sockaddr *)&their_addr, &size);
+ if (fd == -1) {
_get_socket_error();
print_verbose("Error when accepting socket connection.");
return out;
@@ -797,4 +629,4 @@ Error NetSocketPosix::leave_multicast_group(const IPAddress &p_multi_address, co
return _change_multicast_group(p_multi_address, p_if_name, false);
}
-#endif // UNIX_SOCKET_UNAVAILABLE
+#endif // UNIX_ENABLED && !UNIX_SOCKET_UNAVAILABLE
diff --git a/drivers/unix/net_socket_posix.h b/drivers/unix/net_socket_unix.h
index aa59ff36ee..22f7bfdd91 100644
--- a/drivers/unix/net_socket_posix.h
+++ b/drivers/unix/net_socket_unix.h
@@ -1,5 +1,5 @@
/**************************************************************************/
-/* net_socket_posix.h */
+/* net_socket_unix.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,25 +28,18 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
-#ifndef NET_SOCKET_POSIX_H
-#define NET_SOCKET_POSIX_H
+#ifndef NET_SOCKET_UNIX_H
+#define NET_SOCKET_UNIX_H
-#include "core/io/net_socket.h"
+#if defined(UNIX_ENABLED) && !defined(UNIX_SOCKET_UNAVAILABLE)
-#if defined(WINDOWS_ENABLED)
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#define SOCKET_TYPE SOCKET
+#include "core/io/net_socket.h"
-#else
#include <sys/socket.h>
-#define SOCKET_TYPE int
-
-#endif
class NetSocketPosix : public NetSocket {
private:
- SOCKET_TYPE _sock; // NOLINT - the default value is defined in the .cpp
+ int _sock = -1;
IP::Type _ip_type = IP::TYPE_NONE;
bool _is_stream = false;
@@ -61,7 +54,7 @@ private:
};
NetError _get_socket_error() const;
- void _set_socket(SOCKET_TYPE p_sock, IP::Type p_ip_type, bool p_is_stream);
+ void _set_socket(int p_sock, IP::Type p_ip_type, bool p_is_stream);
_FORCE_INLINE_ Error _change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add);
_FORCE_INLINE_ void _set_close_exec_enabled(bool p_enabled);
@@ -76,33 +69,34 @@ public:
static void _set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port);
static size_t _set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type);
- virtual Error open(Type p_sock_type, IP::Type &ip_type);
- virtual void close();
- virtual Error bind(IPAddress p_addr, uint16_t p_port);
- virtual Error listen(int p_max_pending);
- virtual Error connect_to_host(IPAddress p_host, uint16_t p_port);
- virtual Error poll(PollType p_type, int timeout) const;
- virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read);
- virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false);
- virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent);
- virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port);
- virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port);
-
- virtual bool is_open() const;
- virtual int get_available_bytes() const;
- virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const;
-
- virtual Error set_broadcasting_enabled(bool p_enabled);
- virtual void set_blocking_enabled(bool p_enabled);
- virtual void set_ipv6_only_enabled(bool p_enabled);
- virtual void set_tcp_no_delay_enabled(bool p_enabled);
- virtual void set_reuse_address_enabled(bool p_enabled);
- virtual void set_reuse_port_enabled(bool p_enabled);
- virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name);
- virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name);
+ virtual Error open(Type p_sock_type, IP::Type &ip_type) override;
+ virtual void close() override;
+ virtual Error bind(IPAddress p_addr, uint16_t p_port) override;
+ virtual Error listen(int p_max_pending) override;
+ virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override;
+ virtual Error poll(PollType p_type, int timeout) const override;
+ virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override;
+ virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override;
+ virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override;
+ virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override;
+ virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) override;
+
+ virtual bool is_open() const override;
+ virtual int get_available_bytes() const override;
+ virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override;
+
+ virtual Error set_broadcasting_enabled(bool p_enabled) override;
+ virtual void set_blocking_enabled(bool p_enabled) override;
+ virtual void set_ipv6_only_enabled(bool p_enabled) override;
+ virtual void set_tcp_no_delay_enabled(bool p_enabled) override;
+ virtual void set_reuse_address_enabled(bool p_enabled) override;
+ virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override;
+ virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override;
NetSocketPosix();
- ~NetSocketPosix();
+ ~NetSocketPosix() override;
};
-#endif // NET_SOCKET_POSIX_H
+#endif // UNIX_ENABLED && !UNIX_SOCKET_UNAVAILABLE
+
+#endif // NET_SOCKET_UNIX_H
diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp
index 8a9b130068..b23dba7a49 100644
--- a/drivers/unix/os_unix.cpp
+++ b/drivers/unix/os_unix.cpp
@@ -38,7 +38,7 @@
#include "drivers/unix/dir_access_unix.h"
#include "drivers/unix/file_access_unix.h"
#include "drivers/unix/file_access_unix_pipe.h"
-#include "drivers/unix/net_socket_posix.h"
+#include "drivers/unix/net_socket_unix.h"
#include "drivers/unix/thread_posix.h"
#include "servers/rendering_server.h"
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp
index f50771ddde..6eecd850f5 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.cpp
+++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp
@@ -2611,7 +2611,10 @@ Error RenderingDeviceDriverVulkan::command_queue_execute_and_present(CommandQueu
// it'll lead to very low performance in Android by entering an endless loop where it'll always resize the swap chain
// every frame.
- ERR_FAIL_COND_V(err != VK_SUCCESS && err != VK_SUBOPTIMAL_KHR, FAILED);
+ ERR_FAIL_COND_V_MSG(
+ err != VK_SUCCESS && err != VK_SUBOPTIMAL_KHR,
+ FAILED,
+ "QueuePresentKHR failed with error: " + get_vulkan_result(err));
}
return OK;
@@ -5434,6 +5437,23 @@ void RenderingDeviceDriverVulkan::print_lost_device_info() {
on_device_lost();
}
+inline String RenderingDeviceDriverVulkan::get_vulkan_result(VkResult err) {
+#if defined(DEBUG_ENABLED) || defined(DEV_ENABLED)
+ if (err == VK_ERROR_OUT_OF_HOST_MEMORY) {
+ return "VK_ERROR_OUT_OF_HOST_MEMORY";
+ } else if (err == VK_ERROR_OUT_OF_DEVICE_MEMORY) {
+ return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
+ } else if (err == VK_ERROR_DEVICE_LOST) {
+ return "VK_ERROR_DEVICE_LOST";
+ } else if (err == VK_ERROR_SURFACE_LOST_KHR) {
+ return "VK_ERROR_SURFACE_LOST_KHR";
+ } else if (err == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT) {
+ return "VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT";
+ }
+#endif
+ return itos(err);
+}
+
/********************/
/**** SUBMISSION ****/
/********************/
diff --git a/drivers/vulkan/rendering_device_driver_vulkan.h b/drivers/vulkan/rendering_device_driver_vulkan.h
index 6931015a22..06cd2a31be 100644
--- a/drivers/vulkan/rendering_device_driver_vulkan.h
+++ b/drivers/vulkan/rendering_device_driver_vulkan.h
@@ -655,6 +655,7 @@ public:
virtual void command_insert_breadcrumb(CommandBufferID p_cmd_buffer, uint32_t p_data) override final;
void print_lost_device_info();
void on_device_lost() const;
+ static String get_vulkan_result(VkResult err);
/********************/
/**** SUBMISSION ****/
diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp
index 5c06295f14..7d2247d41a 100644
--- a/drivers/windows/file_access_windows.cpp
+++ b/drivers/windows/file_access_windows.cpp
@@ -127,7 +127,7 @@ Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
}
#ifdef TOOLS_ENABLED
- // Windows is case insensitive, but all other platforms are sensitive to it
+ // Windows is case insensitive in the default configuration, but other platforms can be sensitive to it
// To ease cross-platform development, we issue a warning if users try to access
// a file using the wrong case (which *works* on Windows, but won't on other
// platforms), we only check for relative paths, or paths in res:// or user://,
diff --git a/drivers/windows/net_socket_winsock.cpp b/drivers/windows/net_socket_winsock.cpp
new file mode 100644
index 0000000000..3fe7fc619e
--- /dev/null
+++ b/drivers/windows/net_socket_winsock.cpp
@@ -0,0 +1,613 @@
+/**************************************************************************/
+/* net_socket_winsock.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifdef WINDOWS_ENABLED
+
+#include "net_socket_winsock.h"
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+#include <mswsock.h>
+// Workaround missing flag in MinGW
+#if defined(__MINGW32__) && !defined(SIO_UDP_NETRESET)
+#define SIO_UDP_NETRESET _WSAIOW(IOC_VENDOR, 15)
+#endif
+
+size_t NetSocketWinSock::_set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type) {
+ memset(p_addr, 0, sizeof(struct sockaddr_storage));
+ if (p_ip_type == IP::TYPE_IPV6 || p_ip_type == IP::TYPE_ANY) { // IPv6 socket.
+
+ // IPv6 only socket with IPv4 address.
+ ERR_FAIL_COND_V(!p_ip.is_wildcard() && p_ip_type == IP::TYPE_IPV6 && p_ip.is_ipv4(), 0);
+
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)p_addr;
+ addr6->sin6_family = AF_INET6;
+ addr6->sin6_port = htons(p_port);
+ if (p_ip.is_valid()) {
+ memcpy(&addr6->sin6_addr.s6_addr, p_ip.get_ipv6(), 16);
+ } else {
+ addr6->sin6_addr = in6addr_any;
+ }
+ return sizeof(sockaddr_in6);
+ } else { // IPv4 socket.
+
+ // IPv4 socket with IPv6 address.
+ ERR_FAIL_COND_V(!p_ip.is_wildcard() && !p_ip.is_ipv4(), 0);
+
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)p_addr;
+ addr4->sin_family = AF_INET;
+ addr4->sin_port = htons(p_port); // Short, network byte order.
+
+ if (p_ip.is_valid()) {
+ memcpy(&addr4->sin_addr.s_addr, p_ip.get_ipv4(), 4);
+ } else {
+ addr4->sin_addr.s_addr = INADDR_ANY;
+ }
+
+ return sizeof(sockaddr_in);
+ }
+}
+
+void NetSocketWinSock::_set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port) {
+ if (p_addr->ss_family == AF_INET) {
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)p_addr;
+ if (r_ip) {
+ r_ip->set_ipv4((uint8_t *)&(addr4->sin_addr.s_addr));
+ }
+ if (r_port) {
+ *r_port = ntohs(addr4->sin_port);
+ }
+ } else if (p_addr->ss_family == AF_INET6) {
+ struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)p_addr;
+ if (r_ip) {
+ r_ip->set_ipv6(addr6->sin6_addr.s6_addr);
+ }
+ if (r_port) {
+ *r_port = ntohs(addr6->sin6_port);
+ }
+ }
+}
+
+NetSocket *NetSocketWinSock::_create_func() {
+ return memnew(NetSocketWinSock);
+}
+
+void NetSocketWinSock::make_default() {
+ ERR_FAIL_COND(_create != nullptr);
+
+ WSADATA data;
+ WSAStartup(MAKEWORD(2, 2), &data);
+ _create = _create_func;
+}
+
+void NetSocketWinSock::cleanup() {
+ ERR_FAIL_COND(_create == nullptr);
+
+ WSACleanup();
+ _create = nullptr;
+}
+
+NetSocketWinSock::NetSocketWinSock() {
+}
+
+NetSocketWinSock::~NetSocketWinSock() {
+ close();
+}
+
+NetSocketWinSock::NetError NetSocketWinSock::_get_socket_error() const {
+ int err = WSAGetLastError();
+ if (err == WSAEISCONN) {
+ return ERR_NET_IS_CONNECTED;
+ }
+ if (err == WSAEINPROGRESS || err == WSAEALREADY) {
+ return ERR_NET_IN_PROGRESS;
+ }
+ if (err == WSAEWOULDBLOCK) {
+ return ERR_NET_WOULD_BLOCK;
+ }
+ if (err == WSAEADDRINUSE || err == WSAEADDRNOTAVAIL) {
+ return ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE;
+ }
+ if (err == WSAEACCES) {
+ return ERR_NET_UNAUTHORIZED;
+ }
+ if (err == WSAEMSGSIZE || err == WSAENOBUFS) {
+ return ERR_NET_BUFFER_TOO_SMALL;
+ }
+ print_verbose("Socket error: " + itos(err) + ".");
+ return ERR_NET_OTHER;
+}
+
+bool NetSocketWinSock::_can_use_ip(const IPAddress &p_ip, const bool p_for_bind) const {
+ if (p_for_bind && !(p_ip.is_valid() || p_ip.is_wildcard())) {
+ return false;
+ } else if (!p_for_bind && !p_ip.is_valid()) {
+ return false;
+ }
+ // Check if socket support this IP type.
+ IP::Type type = p_ip.is_ipv4() ? IP::TYPE_IPV4 : IP::TYPE_IPV6;
+ return !(_ip_type != IP::TYPE_ANY && !p_ip.is_wildcard() && _ip_type != type);
+}
+
+_FORCE_INLINE_ Error NetSocketWinSock::_change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!_can_use_ip(p_ip, false), ERR_INVALID_PARAMETER);
+
+ // Need to force level and af_family to IP(v4) when using dual stacking and provided multicast group is IPv4.
+ IP::Type type = _ip_type == IP::TYPE_ANY && p_ip.is_ipv4() ? IP::TYPE_IPV4 : _ip_type;
+ // This needs to be the proper level for the multicast group, no matter if the socket is dual stacking.
+ int level = type == IP::TYPE_IPV4 ? IPPROTO_IP : IPPROTO_IPV6;
+ int ret = -1;
+
+ IPAddress if_ip;
+ uint32_t if_v6id = 0;
+ HashMap<String, IP::Interface_Info> if_info;
+ IP::get_singleton()->get_local_interfaces(&if_info);
+ for (KeyValue<String, IP::Interface_Info> &E : if_info) {
+ IP::Interface_Info &c = E.value;
+ if (c.name != p_if_name) {
+ continue;
+ }
+
+ if_v6id = (uint32_t)c.index.to_int();
+ if (type == IP::TYPE_IPV6) {
+ break; // IPv6 uses index.
+ }
+
+ for (const IPAddress &F : c.ip_addresses) {
+ if (!F.is_ipv4()) {
+ continue; // Wrong IP type.
+ }
+ if_ip = F;
+ break;
+ }
+ break;
+ }
+
+ if (level == IPPROTO_IP) {
+ ERR_FAIL_COND_V(!if_ip.is_valid(), ERR_INVALID_PARAMETER);
+ struct ip_mreq greq;
+ int sock_opt = p_add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
+ memcpy(&greq.imr_multiaddr, p_ip.get_ipv4(), 4);
+ memcpy(&greq.imr_interface, if_ip.get_ipv4(), 4);
+ ret = setsockopt(_sock, level, sock_opt, (const char *)&greq, sizeof(greq));
+ } else {
+ struct ipv6_mreq greq;
+ int sock_opt = p_add ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP;
+ memcpy(&greq.ipv6mr_multiaddr, p_ip.get_ipv6(), 16);
+ greq.ipv6mr_interface = if_v6id;
+ ret = setsockopt(_sock, level, sock_opt, (const char *)&greq, sizeof(greq));
+ }
+ ERR_FAIL_COND_V(ret != 0, FAILED);
+
+ return OK;
+}
+
+void NetSocketWinSock::_set_socket(SOCKET p_sock, IP::Type p_ip_type, bool p_is_stream) {
+ _sock = p_sock;
+ _ip_type = p_ip_type;
+ _is_stream = p_is_stream;
+}
+
+Error NetSocketWinSock::open(Type p_sock_type, IP::Type &ip_type) {
+ ERR_FAIL_COND_V(is_open(), ERR_ALREADY_IN_USE);
+ ERR_FAIL_COND_V(ip_type > IP::TYPE_ANY || ip_type < IP::TYPE_NONE, ERR_INVALID_PARAMETER);
+
+ int family = ip_type == IP::TYPE_IPV4 ? AF_INET : AF_INET6;
+ int protocol = p_sock_type == TYPE_TCP ? IPPROTO_TCP : IPPROTO_UDP;
+ int type = p_sock_type == TYPE_TCP ? SOCK_STREAM : SOCK_DGRAM;
+ _sock = socket(family, type, protocol);
+
+ if (_sock == INVALID_SOCKET && ip_type == IP::TYPE_ANY) {
+ // Careful here, changing the referenced parameter so the caller knows that we are using an IPv4 socket
+ // in place of a dual stack one, and further calls to _set_sock_addr will work as expected.
+ ip_type = IP::TYPE_IPV4;
+ family = AF_INET;
+ _sock = socket(family, type, protocol);
+ }
+
+ ERR_FAIL_COND_V(_sock == INVALID_SOCKET, FAILED);
+ _ip_type = ip_type;
+
+ if (family == AF_INET6) {
+ // Select IPv4 over IPv6 mapping.
+ set_ipv6_only_enabled(ip_type != IP::TYPE_ANY);
+ }
+
+ if (protocol == IPPROTO_UDP) {
+ // Make sure to disable broadcasting for UDP sockets.
+ // Depending on the OS, this option might or might not be enabled by default. Let's normalize it.
+ set_broadcasting_enabled(false);
+ }
+
+ _is_stream = p_sock_type == TYPE_TCP;
+
+ if (!_is_stream) {
+ // Disable windows feature/bug reporting WSAECONNRESET/WSAENETRESET when
+ // recv/recvfrom and an ICMP reply was received from a previous send/sendto.
+ unsigned long disable = 0;
+ if (ioctlsocket(_sock, SIO_UDP_CONNRESET, &disable) == SOCKET_ERROR) {
+ print_verbose("Unable to turn off UDP WSAECONNRESET behavior on Windows.");
+ }
+ if (ioctlsocket(_sock, SIO_UDP_NETRESET, &disable) == SOCKET_ERROR) {
+ // This feature seems not to be supported on wine.
+ print_verbose("Unable to turn off UDP WSAENETRESET behavior on Windows.");
+ }
+ }
+ return OK;
+}
+
+void NetSocketWinSock::close() {
+ if (_sock != INVALID_SOCKET) {
+ closesocket(_sock);
+ }
+
+ _sock = INVALID_SOCKET;
+ _ip_type = IP::TYPE_NONE;
+ _is_stream = false;
+}
+
+Error NetSocketWinSock::bind(IPAddress p_addr, uint16_t p_port) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!_can_use_ip(p_addr, true), ERR_INVALID_PARAMETER);
+
+ sockaddr_storage addr;
+ size_t addr_size = _set_addr_storage(&addr, p_addr, p_port, _ip_type);
+
+ if (::bind(_sock, (struct sockaddr *)&addr, addr_size) != 0) {
+ NetError err = _get_socket_error();
+ print_verbose("Failed to bind socket. Error: " + itos(err) + ".");
+ close();
+ return ERR_UNAVAILABLE;
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::listen(int p_max_pending) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+
+ if (::listen(_sock, p_max_pending) != 0) {
+ _get_socket_error();
+ print_verbose("Failed to listen from socket.");
+ close();
+ return FAILED;
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::connect_to_host(IPAddress p_host, uint16_t p_port) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!_can_use_ip(p_host, false), ERR_INVALID_PARAMETER);
+
+ struct sockaddr_storage addr;
+ size_t addr_size = _set_addr_storage(&addr, p_host, p_port, _ip_type);
+
+ if (::WSAConnect(_sock, (struct sockaddr *)&addr, addr_size, nullptr, nullptr, nullptr, nullptr) != 0) {
+ NetError err = _get_socket_error();
+
+ switch (err) {
+ // We are already connected.
+ case ERR_NET_IS_CONNECTED:
+ return OK;
+ // Still waiting to connect, try again in a while.
+ case ERR_NET_WOULD_BLOCK:
+ case ERR_NET_IN_PROGRESS:
+ return ERR_BUSY;
+ default:
+ print_verbose("Connection to remote host failed.");
+ close();
+ return FAILED;
+ }
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::poll(PollType p_type, int p_timeout) const {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+
+ bool ready = false;
+ fd_set rd, wr, ex;
+ fd_set *rdp = nullptr;
+ fd_set *wrp = nullptr;
+ FD_ZERO(&rd);
+ FD_ZERO(&wr);
+ FD_ZERO(&ex);
+ FD_SET(_sock, &ex);
+ struct timeval timeout = { p_timeout / 1000, (p_timeout % 1000) * 1000 };
+ // For blocking operation, pass nullptr timeout pointer to select.
+ struct timeval *tp = nullptr;
+ if (p_timeout >= 0) {
+ // If timeout is non-negative, we want to specify the timeout instead.
+ tp = &timeout;
+ }
+
+ switch (p_type) {
+ case POLL_TYPE_IN:
+ FD_SET(_sock, &rd);
+ rdp = &rd;
+ break;
+ case POLL_TYPE_OUT:
+ FD_SET(_sock, &wr);
+ wrp = &wr;
+ break;
+ case POLL_TYPE_IN_OUT:
+ FD_SET(_sock, &rd);
+ FD_SET(_sock, &wr);
+ rdp = &rd;
+ wrp = &wr;
+ }
+ // WSAPoll is broken: https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/.
+ int ret = select(1, rdp, wrp, &ex, tp);
+
+ if (ret == SOCKET_ERROR) {
+ return FAILED;
+ }
+
+ if (ret == 0) {
+ return ERR_BUSY;
+ }
+
+ if (FD_ISSET(_sock, &ex)) {
+ _get_socket_error();
+ print_verbose("Exception when polling socket.");
+ return FAILED;
+ }
+
+ if (rdp && FD_ISSET(_sock, rdp)) {
+ ready = true;
+ }
+ if (wrp && FD_ISSET(_sock, wrp)) {
+ ready = true;
+ }
+
+ return ready ? OK : ERR_BUSY;
+}
+
+Error NetSocketWinSock::recv(uint8_t *p_buffer, int p_len, int &r_read) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+
+ r_read = ::recv(_sock, (char *)p_buffer, p_len, 0);
+
+ if (r_read < 0) {
+ NetError err = _get_socket_error();
+ if (err == ERR_NET_WOULD_BLOCK) {
+ return ERR_BUSY;
+ }
+
+ if (err == ERR_NET_BUFFER_TOO_SMALL) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ return FAILED;
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+
+ struct sockaddr_storage from;
+ socklen_t len = sizeof(struct sockaddr_storage);
+ memset(&from, 0, len);
+
+ r_read = ::recvfrom(_sock, (char *)p_buffer, p_len, p_peek ? MSG_PEEK : 0, (struct sockaddr *)&from, &len);
+
+ if (r_read < 0) {
+ NetError err = _get_socket_error();
+ if (err == ERR_NET_WOULD_BLOCK) {
+ return ERR_BUSY;
+ }
+
+ if (err == ERR_NET_BUFFER_TOO_SMALL) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ return FAILED;
+ }
+
+ if (from.ss_family == AF_INET) {
+ struct sockaddr_in *sin_from = (struct sockaddr_in *)&from;
+ r_ip.set_ipv4((uint8_t *)&sin_from->sin_addr);
+ r_port = ntohs(sin_from->sin_port);
+ } else if (from.ss_family == AF_INET6) {
+ struct sockaddr_in6 *s6_from = (struct sockaddr_in6 *)&from;
+ r_ip.set_ipv6((uint8_t *)&s6_from->sin6_addr);
+ r_port = ntohs(s6_from->sin6_port);
+ } else {
+ // Unsupported socket family, should never happen.
+ ERR_FAIL_V(FAILED);
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::send(const uint8_t *p_buffer, int p_len, int &r_sent) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+
+ int flags = 0;
+ r_sent = ::send(_sock, (const char *)p_buffer, p_len, flags);
+
+ if (r_sent < 0) {
+ NetError err = _get_socket_error();
+ if (err == ERR_NET_WOULD_BLOCK) {
+ return ERR_BUSY;
+ }
+ if (err == ERR_NET_BUFFER_TOO_SMALL) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ return FAILED;
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+
+ struct sockaddr_storage addr;
+ size_t addr_size = _set_addr_storage(&addr, p_ip, p_port, _ip_type);
+ r_sent = ::sendto(_sock, (const char *)p_buffer, p_len, 0, (struct sockaddr *)&addr, addr_size);
+
+ if (r_sent < 0) {
+ NetError err = _get_socket_error();
+ if (err == ERR_NET_WOULD_BLOCK) {
+ return ERR_BUSY;
+ }
+ if (err == ERR_NET_BUFFER_TOO_SMALL) {
+ return ERR_OUT_OF_MEMORY;
+ }
+
+ return FAILED;
+ }
+
+ return OK;
+}
+
+Error NetSocketWinSock::set_broadcasting_enabled(bool p_enabled) {
+ ERR_FAIL_COND_V(!is_open(), ERR_UNCONFIGURED);
+ // IPv6 has no broadcast support.
+ if (_ip_type == IP::TYPE_IPV6) {
+ return ERR_UNAVAILABLE;
+ }
+
+ int par = p_enabled ? 1 : 0;
+ if (setsockopt(_sock, SOL_SOCKET, SO_BROADCAST, (const char *)&par, sizeof(int)) != 0) {
+ WARN_PRINT("Unable to change broadcast setting.");
+ return FAILED;
+ }
+ return OK;
+}
+
+void NetSocketWinSock::set_blocking_enabled(bool p_enabled) {
+ ERR_FAIL_COND(!is_open());
+
+ int ret = 0;
+ unsigned long par = p_enabled ? 0 : 1;
+ ret = ioctlsocket(_sock, FIONBIO, &par);
+ if (ret != 0) {
+ WARN_PRINT("Unable to change non-block mode.");
+ }
+}
+
+void NetSocketWinSock::set_ipv6_only_enabled(bool p_enabled) {
+ ERR_FAIL_COND(!is_open());
+ // This option is only available in IPv6 sockets.
+ ERR_FAIL_COND(_ip_type == IP::TYPE_IPV4);
+
+ int par = p_enabled ? 1 : 0;
+ if (setsockopt(_sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char *)&par, sizeof(int)) != 0) {
+ WARN_PRINT("Unable to change IPv4 address mapping over IPv6 option.");
+ }
+}
+
+void NetSocketWinSock::set_tcp_no_delay_enabled(bool p_enabled) {
+ ERR_FAIL_COND(!is_open());
+ ERR_FAIL_COND(!_is_stream); // Not TCP.
+
+ int par = p_enabled ? 1 : 0;
+ if (setsockopt(_sock, IPPROTO_TCP, TCP_NODELAY, (const char *)&par, sizeof(int)) < 0) {
+ WARN_PRINT("Unable to set TCP no delay option.");
+ }
+}
+
+void NetSocketWinSock::set_reuse_address_enabled(bool p_enabled) {
+ ERR_FAIL_COND(!is_open());
+
+ // On Windows, enabling SO_REUSEADDR actually would also enable reuse port, very bad on TCP. Denying...
+ // Windows does not have this option, SO_REUSEADDR in this magical world means SO_REUSEPORT
+}
+
+bool NetSocketWinSock::is_open() const {
+ return _sock != INVALID_SOCKET;
+}
+
+int NetSocketWinSock::get_available_bytes() const {
+ ERR_FAIL_COND_V(!is_open(), -1);
+
+ unsigned long len;
+ int ret = ioctlsocket(_sock, FIONREAD, &len);
+ if (ret == -1) {
+ _get_socket_error();
+ print_verbose("Error when checking available bytes on socket.");
+ return -1;
+ }
+ return len;
+}
+
+Error NetSocketWinSock::get_socket_address(IPAddress *r_ip, uint16_t *r_port) const {
+ ERR_FAIL_COND_V(!is_open(), FAILED);
+
+ struct sockaddr_storage saddr;
+ socklen_t len = sizeof(saddr);
+ if (getsockname(_sock, (struct sockaddr *)&saddr, &len) != 0) {
+ _get_socket_error();
+ print_verbose("Error when reading local socket address.");
+ return FAILED;
+ }
+ _set_ip_port(&saddr, r_ip, r_port);
+ return OK;
+}
+
+Ref<NetSocket> NetSocketWinSock::accept(IPAddress &r_ip, uint16_t &r_port) {
+ Ref<NetSocket> out;
+ ERR_FAIL_COND_V(!is_open(), out);
+
+ struct sockaddr_storage their_addr;
+ socklen_t size = sizeof(their_addr);
+ SOCKET fd = ::accept(_sock, (struct sockaddr *)&their_addr, &size);
+ if (fd == INVALID_SOCKET) {
+ _get_socket_error();
+ print_verbose("Error when accepting socket connection.");
+ return out;
+ }
+
+ _set_ip_port(&their_addr, &r_ip, &r_port);
+
+ NetSocketWinSock *ns = memnew(NetSocketWinSock);
+ ns->_set_socket(fd, _ip_type, _is_stream);
+ ns->set_blocking_enabled(false);
+ return Ref<NetSocket>(ns);
+}
+
+Error NetSocketWinSock::join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) {
+ return _change_multicast_group(p_multi_address, p_if_name, true);
+}
+
+Error NetSocketWinSock::leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) {
+ return _change_multicast_group(p_multi_address, p_if_name, false);
+}
+
+#endif // WINDOWS_ENABLED
diff --git a/drivers/windows/net_socket_winsock.h b/drivers/windows/net_socket_winsock.h
new file mode 100644
index 0000000000..5c3445b8cb
--- /dev/null
+++ b/drivers/windows/net_socket_winsock.h
@@ -0,0 +1,102 @@
+/**************************************************************************/
+/* net_socket_winsock.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef NET_SOCKET_WINSOCK_H
+#define NET_SOCKET_WINSOCK_H
+
+#ifdef WINDOWS_ENABLED
+
+#include "core/io/net_socket.h"
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+class NetSocketWinSock : public NetSocket {
+private:
+ SOCKET _sock = INVALID_SOCKET;
+ IP::Type _ip_type = IP::TYPE_NONE;
+ bool _is_stream = false;
+
+ enum NetError {
+ ERR_NET_WOULD_BLOCK,
+ ERR_NET_IS_CONNECTED,
+ ERR_NET_IN_PROGRESS,
+ ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE,
+ ERR_NET_UNAUTHORIZED,
+ ERR_NET_BUFFER_TOO_SMALL,
+ ERR_NET_OTHER,
+ };
+
+ NetError _get_socket_error() const;
+ void _set_socket(SOCKET p_sock, IP::Type p_ip_type, bool p_is_stream);
+ _FORCE_INLINE_ Error _change_multicast_group(IPAddress p_ip, String p_if_name, bool p_add);
+
+protected:
+ static NetSocket *_create_func();
+
+ bool _can_use_ip(const IPAddress &p_ip, const bool p_for_bind) const;
+
+public:
+ static void make_default();
+ static void cleanup();
+ static void _set_ip_port(struct sockaddr_storage *p_addr, IPAddress *r_ip, uint16_t *r_port);
+ static size_t _set_addr_storage(struct sockaddr_storage *p_addr, const IPAddress &p_ip, uint16_t p_port, IP::Type p_ip_type);
+
+ virtual Error open(Type p_sock_type, IP::Type &ip_type) override;
+ virtual void close() override;
+ virtual Error bind(IPAddress p_addr, uint16_t p_port) override;
+ virtual Error listen(int p_max_pending) override;
+ virtual Error connect_to_host(IPAddress p_host, uint16_t p_port) override;
+ virtual Error poll(PollType p_type, int timeout) const override;
+ virtual Error recv(uint8_t *p_buffer, int p_len, int &r_read) override;
+ virtual Error recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddress &r_ip, uint16_t &r_port, bool p_peek = false) override;
+ virtual Error send(const uint8_t *p_buffer, int p_len, int &r_sent) override;
+ virtual Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) override;
+ virtual Ref<NetSocket> accept(IPAddress &r_ip, uint16_t &r_port) override;
+
+ virtual bool is_open() const override;
+ virtual int get_available_bytes() const override;
+ virtual Error get_socket_address(IPAddress *r_ip, uint16_t *r_port) const override;
+
+ virtual Error set_broadcasting_enabled(bool p_enabled) override;
+ virtual void set_blocking_enabled(bool p_enabled) override;
+ virtual void set_ipv6_only_enabled(bool p_enabled) override;
+ virtual void set_tcp_no_delay_enabled(bool p_enabled) override;
+ virtual void set_reuse_address_enabled(bool p_enabled) override;
+ virtual Error join_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override;
+ virtual Error leave_multicast_group(const IPAddress &p_multi_address, const String &p_if_name) override;
+
+ NetSocketWinSock();
+ ~NetSocketWinSock() override;
+};
+
+#endif // WINDOWS_ENABLED
+
+#endif // NET_SOCKET_WINSOCK_H