102 lines
6.1 KiB
Python
102 lines
6.1 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 CommandList7 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
|
|
|
|
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 += [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('')
|
|
lines += ['static void attach_recording_methods(void** table, unsigned version) {'] + registrations + ['}', '']
|
|
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')
|