From 4fe672038dff326f33197e2492bdeeb682d7632b Mon Sep 17 00:00:00 2001 From: Roberto Nibali Date: Wed, 8 Jul 2026 13:20:51 +0200 Subject: [PATCH] winemetal: validate render draw state before encoding Guard render draw commands before passing them to Metal. If pipeline creation failed or an indexed draw carries an invalid index buffer/range, skip the draw and report the condition instead of letting Objective-C Metal validation raise an exception from the encoder. Persist whether a render encoder has seen a valid pipeline state across command batches by attaching the state to the encoder object. Signed-off-by: Roberto Nibali --- .../src/winemetal/unix/winemetal_unix.c | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/src/libs/dxmt-0.60/src/winemetal/unix/winemetal_unix.c b/src/libs/dxmt-0.60/src/winemetal/unix/winemetal_unix.c index e78f13f79ffc..a7fcb2bede43 100644 --- a/src/libs/dxmt-0.60/src/winemetal/unix/winemetal_unix.c +++ b/src/libs/dxmt-0.60/src/winemetal/unix/winemetal_unix.c @@ -6,6 +6,9 @@ # import #endif #import +#include +#include +#include #include "objc/objc-runtime.h" #define WINEMETAL_API #include "../winemetal_thunks.h" @@ -23,6 +26,74 @@ execute_on_main(dispatch_block_t block) { } } +static bool +validate_indexed_draw_buffer( + obj_handle_t buffer_handle, enum WMTIndexType index_type, uint64_t index_buffer_offset, uint64_t index_count, + const char *command_name +) { + id index_buffer = (id)buffer_handle; + if (!index_buffer) { + fprintf(stderr, "DXMT: skipping %s with null index buffer\n", command_name); + return false; + } + if (index_type != WMTIndexTypeUInt16 && index_type != WMTIndexTypeUInt32) { + fprintf(stderr, "DXMT: skipping %s with invalid index type %u\n", command_name, (unsigned)index_type); + return false; + } + + const uint64_t index_size = index_type == WMTIndexTypeUInt32 ? 4 : 2; + const uint64_t index_buffer_length = [index_buffer length]; + if (index_count > (UINT64_MAX - index_buffer_offset) / index_size) { + fprintf( + stderr, + "DXMT: skipping %s with overflowing index range offset=%" PRIu64 " count=%" PRIu64 " index_size=%" PRIu64 + " length=%" PRIu64 "\n", + command_name, index_buffer_offset, index_count, index_size, index_buffer_length + ); + return false; + } + + const uint64_t index_range_end = index_buffer_offset + index_count * index_size; + if (index_buffer_offset > index_buffer_length || index_range_end > index_buffer_length) { + fprintf( + stderr, + "DXMT: skipping %s with out-of-bounds index range offset=%" PRIu64 " count=%" PRIu64 + " index_size=%" PRIu64 " length=%" PRIu64 "\n", + command_name, index_buffer_offset, index_count, index_size, index_buffer_length + ); + return false; + } + + return true; +} + +static char g_render_encoder_has_pipeline_state_key; + +static bool +render_encoder_has_pipeline_state(id encoder) { + NSNumber *state = objc_getAssociatedObject(encoder, &g_render_encoder_has_pipeline_state_key); + return state ? [state boolValue] : false; +} + +static void +render_encoder_set_pipeline_state_valid(id encoder, bool valid) { + objc_setAssociatedObject( + encoder, &g_render_encoder_has_pipeline_state_key, [NSNumber numberWithBool:valid], OBJC_ASSOCIATION_RETAIN_NONATOMIC + ); +} + +static bool +validate_render_pipeline_state(bool pipeline_state_valid, bool *reported_missing_pso) { + if (pipeline_state_valid) + return true; + + if (!*reported_missing_pso) { + fprintf(stderr, "DXMT: skipping render draws without a valid pipeline state\n"); + *reported_missing_pso = true; + } + return false; +} + static NTSTATUS _NSObject_retain(NSObject **obj) { [*obj retain]; @@ -738,6 +809,8 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { struct unixcall_generic_obj_cmd_noret *params = obj; const struct wmtcmd_base *next = params->cmd_head.ptr; id encoder = (id)params->encoder; + bool render_pipeline_state_valid = render_encoder_has_pipeline_state(encoder); + bool reported_missing_pso = false; while (next) { switch ((enum WMTRenderCommandType)next->type) { default: @@ -823,7 +896,15 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { } case WMTRenderCommandSetPSO: { struct wmtcmd_render_setpso *body = (struct wmtcmd_render_setpso *)next; + if (!body->pso) { + render_pipeline_state_valid = false; + render_encoder_set_pipeline_state_valid(encoder, false); + fprintf(stderr, "DXMT: skipping null render pipeline state\n"); + break; + } [encoder setRenderPipelineState:(id)body->pso]; + render_pipeline_state_valid = true; + render_encoder_set_pipeline_state_valid(encoder, true); break; } case WMTRenderCommandSetDSSO: { @@ -845,6 +926,8 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { } case WMTRenderCommandDraw: { struct wmtcmd_render_draw *body = (struct wmtcmd_render_draw *)next; + if (!validate_render_pipeline_state(render_pipeline_state_valid, &reported_missing_pso)) + break; [encoder drawPrimitives:(MTLPrimitiveType)body->primitive_type vertexStart:body->vertex_start vertexCount:body->vertex_count @@ -854,6 +937,12 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { } case WMTRenderCommandDrawIndexed: { struct wmtcmd_render_draw_indexed *body = (struct wmtcmd_render_draw_indexed *)next; + if (!validate_render_pipeline_state(render_pipeline_state_valid, &reported_missing_pso)) + break; + if (!validate_indexed_draw_buffer( + body->index_buffer, body->index_type, body->index_buffer_offset, body->index_count, "DrawIndexed" + )) + break; [encoder drawIndexedPrimitives:(MTLPrimitiveType)body->primitive_type indexCount:body->index_count indexType:(MTLIndexType)body->index_type @@ -866,6 +955,8 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { } case WMTRenderCommandDrawIndirect: { struct wmtcmd_render_draw_indirect *body = (struct wmtcmd_render_draw_indirect *)next; + if (!validate_render_pipeline_state(render_pipeline_state_valid, &reported_missing_pso)) + break; [encoder drawPrimitives:(MTLPrimitiveType)body->primitive_type indirectBuffer:(id)body->indirect_args_buffer indirectBufferOffset:body->indirect_args_offset]; @@ -873,6 +964,23 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { } case WMTRenderCommandDrawIndexedIndirect: { struct wmtcmd_render_draw_indexed_indirect *body = (struct wmtcmd_render_draw_indexed_indirect *)next; + if (!validate_render_pipeline_state(render_pipeline_state_valid, &reported_missing_pso)) + break; + if (!validate_indexed_draw_buffer( + body->index_buffer, body->index_type, body->index_buffer_offset, 0, "DrawIndexedIndirect" + )) + break; + id indirect_args_buffer = (id)body->indirect_args_buffer; + if (!indirect_args_buffer || body->indirect_args_offset > [indirect_args_buffer length] || + [indirect_args_buffer length] - body->indirect_args_offset < 5 * sizeof(uint32_t)) { + fprintf( + stderr, + "DXMT: skipping DrawIndexedIndirect with invalid indirect buffer offset=%" PRIu64 " length=%" PRIu64 + "\n", + body->indirect_args_offset, indirect_args_buffer ? (uint64_t)[indirect_args_buffer length] : 0 + ); + break; + } [encoder drawIndexedPrimitives:(MTLPrimitiveType)body->primitive_type indexType:(MTLIndexType)body->index_type indexBuffer:(id)body->index_buffer @@ -883,6 +991,8 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { } case WMTRenderCommandDrawMeshThreadgroups: { struct wmtcmd_render_draw_meshthreadgroups *body = (struct wmtcmd_render_draw_meshthreadgroups *)next; + if (!validate_render_pipeline_state(render_pipeline_state_valid, &reported_missing_pso)) + break; [encoder drawMeshThreadgroups:MTLSizeMake( body->threadgroup_per_grid.width, body->threadgroup_per_grid.height, body->threadgroup_per_grid.depth @@ -900,6 +1010,8 @@ _MTLRenderCommandEncoder_encodeCommands(void *obj) { case WMTRenderCommandDrawMeshThreadgroupsIndirect: { struct wmtcmd_render_draw_meshthreadgroups_indirect *body = (struct wmtcmd_render_draw_meshthreadgroups_indirect *)next; + if (!validate_render_pipeline_state(render_pipeline_state_valid, &reported_missing_pso)) + break; [encoder drawMeshThreadgroupsWithIndirectBuffer:(id)body->indirect_args_buffer indirectBufferOffset:body->indirect_args_offset threadsPerObjectThreadgroup:MTLSizeMake(