"""Generate typed command forwarding from public MinGW D3D12 SDK declarations. Run with --header --output d3d12_command_hooks.generated.h. Every recording method through CommandList10 is forwarded. State setters copy their host arguments; COM object arguments retain a reference. SDK slot numbers are derived from the public interface inheritance, never a driver binary. """ import argparse import pathlib import re # Public additions verified against Microsoft's DirectX-Headers adbd6f3b. # The old MinGW SDK has the needed scalar types but lacks the work-graph # descriptor names; NewProgramDescription is a typed public-ABI mirror. NEWER = [ (8, 'OMSetFrontAndBackStencilRef', 'UINT front_stencil, UINT back_stencil'), (9, 'RSSetDepthBias', 'FLOAT depth_bias, FLOAT clamp, FLOAT slope'), (9, 'IASetIndexBufferStripCutValue', 'D3D12_INDEX_BUFFER_STRIP_CUT_VALUE value'), (10, 'SetProgram', 'const NewProgramDescription* desc'), (10, 'DispatchGraph', 'const void* desc'), ] POINTER_COUNTS = { 'RSSetViewports': {'viewports': 'viewport_count'}, 'RSSetScissorRects': {'rects': 'rect_count'}, 'OMSetBlendFactor': {'blend_factor': '4'}, 'SetDescriptorHeaps': {'heaps': 'heap_count'}, 'SetComputeRoot32BitConstants': {'data': 'constant_count'}, 'SetGraphicsRoot32BitConstants': {'data': 'constant_count'}, 'IASetIndexBuffer': {'view': '1'}, 'IASetVertexBuffers': {'views': 'view_count'}, 'SOSetTargets': {'views': 'view_count'}, 'OMSetRenderTargets': {'render_target_descriptors': '(single_descriptor_handle ? 1 : render_target_descriptor_count)', 'depth_stencil_descriptor': '1'}, 'SetSamplePositions': {'sample_positions': 'sample_count * pixel_count'}, 'RSSetShadingRate': {'combiners': '2'}, } SPECIAL = {'Close', 'Reset', 'ClearState', 'ResourceBarrier', 'Barrier', 'BeginEvent', 'EndEvent', 'BeginRenderPass', 'EndRenderPass', 'BeginQuery', 'EndQuery', 'ExecuteBundle', 'ExecuteIndirect', 'SetPredication'} def generate(source): classes = [] for match in re.finditer(r'\n(ID3D12GraphicsCommandList\d*) : public \w+\s*\{(.*?)\n\};', source, re.S): name, body = match.groups() methods = re.findall(r'virtual\s+(.*?)\s+STDMETHODCALLTYPE\s+(\w+)\s*\((.*?)\)\s*= 0;', body, re.S) classes.append((int(name[len('ID3D12GraphicsCommandList'):] or '0'), name, methods)) if [version for version, _, _ in sorted(classes)] != list(range(8)): raise RuntimeError('Expected exactly the public CommandList0..7 interfaces; review a changed SDK') lines = ['// Generated by generate_d3d12_hooks.py from the public SDK. Do not edit.', ''] registrations = [] slot = 9 # IUnknown(3), ID3D12Object(4), DeviceChild(1), CommandList(1). for version, interface, methods in sorted(classes): for result, name, arguments in methods: # MinGW WINBOOL and Microsoft SDK BOOL have the same ABI. result = ' '.join(result.split()).replace('WINBOOL', 'BOOL') arguments = ' '.join(arguments.split()).replace('WINBOOL', 'BOOL') declarations = [arg.strip() for arg in arguments.split(',')] if arguments else [] names = [] for declaration in declarations: match = re.search(r'(\w+)\s*(?:\[\d+\])?$', declaration) if not match: raise RuntimeError(f'Unparsed SDK argument: {name}: {declaration}') names.append(match[1]) signature = ', '.join(['ID3D12GraphicsCommandList* self'] + declarations) call = ', '.join(['access.target()'] + names) lines.append(f'using Fn_{name} = {result}(STDMETHODCALLTYPE*)({signature});') lines.append(f'static Fn_{name} original_{name} = nullptr;') if name in SPECIAL: lines.append(f'static {result} STDMETHODCALLTYPE hook_{name}({signature});') else: lines += [f'static {result} STDMETHODCALLTYPE hook_{name}({signature}) {{', ' Access access(self);'] stateful = name.startswith(('Set', 'IASet', 'RSSet', 'OMSet', 'SOSet')) and name != 'SetMarker' if stateful: captured = [] for declaration, argument in zip(declarations, names): pointer = '*' in declaration or '[' in declaration if pointer and 'ID3D12' in declaration: if declaration.count('*') > 1: count = POINTER_COUNTS.get(name, {}).get(argument) if count is None: raise RuntimeError(f'No COM array copy rule: {name}: {declaration}') lines.append(f' auto saved_{argument} = retain_values({argument}, {count});') captured.append(f'saved_{argument}.raw.data()') else: lines.append(f' auto saved_{argument} = retain({argument});') captured.append(f'saved_{argument}.Get()') elif pointer: count = POINTER_COUNTS.get(name, {}).get(argument) if count is None: raise RuntimeError(f'No deep-copy rule for {name}: {declaration}') value = f'static_cast({argument})' if argument == 'data' else argument lines.append(f' auto saved_{argument} = copy_values({value}, {count});') captured.append(f'values_pointer(saved_{argument})') else: captured.append(argument) lines += [' if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) mutable {', f' original_{name}(' + ', '.join(['target'] + captured) + ');', ' });'] lines += [' access.driver_call();', f' return original_{name}({call});', '}'] registrations.append(f' attach_method(table[{slot}], reinterpret_cast(hook_{name}), reinterpret_cast(&original_{name}), "{name}");') registrations[-1] = f' if (version >= {version})' + registrations[-1][3:] slot += 1 lines.append('') for version, name, arguments in NEWER: declarations = arguments.split(', ') names = [re.search(r'(\w+)$', argument)[1] for argument in declarations] signature = ', '.join(['ID3D12GraphicsCommandList* self'] + declarations) lines += [f'using Fn_{name} = void(STDMETHODCALLTYPE*)({signature});', f'static Fn_{name} original_{name} = nullptr;'] if name == 'SetProgram': lines.append(f'static void STDMETHODCALLTYPE hook_{name}({signature});') else: lines += [f'static void STDMETHODCALLTYPE hook_{name}({signature}) {{', ' Access access(self);'] if name != 'DispatchGraph': lines += [' if (access.recording) access.recording->state.emplace_back([=](ID3D12GraphicsCommandList* target) {', f' original_{name}(' + ', '.join(['target'] + names) + ');', ' });'] lines += [' access.driver_call();', f' original_{name}(' + ', '.join(['access.target()'] + names) + ');', '}'] registrations.append(f' if (version >= {version}) attach_method(table[{slot}], reinterpret_cast(hook_{name}), reinterpret_cast(&original_{name}), "{name}");') slot += 1 lines.append('') lines += ['static void attach_recording_methods(void** table, unsigned version) {'] + registrations + ['}', ''] device = re.search(r'typedef struct ID3D12DeviceVtbl \{(.*?)\n\} ID3D12DeviceVtbl;', source, re.S)[1] fields = list(dict.fromkeys(re.findall(r'\(STDMETHODCALLTYPE \*(\w+)\)', device))) lines.append(f'static constexpr unsigned device_signature_slot = {fields.index("CreateCommandSignature")};') lines.append(f'static constexpr unsigned device_command_list_slot = {fields.index("CreateCommandList")};') queue = re.search(r'typedef struct ID3D12CommandQueueVtbl \{(.*?)\n\} ID3D12CommandQueueVtbl;', source, re.S)[1] queue_fields = list(dict.fromkeys(re.findall(r'\(STDMETHODCALLTYPE \*(\w+)\)', queue))) for name in ('Signal', 'Wait'): lines.append(f'static constexpr unsigned queue_{name.lower()}_slot = {queue_fields.index(name)};') return '\n'.join(lines) if __name__ == '__main__': parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--header', type=pathlib.Path, required=True) parser.add_argument('--output', type=pathlib.Path, required=True) args = parser.parse_args() args.output.write_text(generate(args.header.read_text()), encoding='utf-8')