Files
d4r/tools/windows/generate_d3d12_hooks.py
T

137 lines
8.6 KiB
Python

"""Generate typed command forwarding from public MinGW D3D12 SDK declarations.
Run with --header <SDK/include/d3d12.h> --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:
result = ' '.join(result.split())
arguments = ' '.join(arguments.split())
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<const UINT*>({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<void*>(hook_{name}), reinterpret_cast<void**>(&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<void*>(hook_{name}), reinterpret_cast<void**>(&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')