Files
d4r/tools/windows/d3d12_external.h
T

191 lines
9.8 KiB
C++

#pragma once
#if defined(__MINGW32__)
#define WIDL_EXPLICIT_AGGREGATE_RETURNS
#endif
#include "hip_api.h"
#include <d3d12.h>
#include <wrl/client.h>
namespace d4r::win {
using Microsoft::WRL::ComPtr;
using diag::HipApi;
inline void dx(HRESULT result, const char* operation) {
if (FAILED(result)) throw std::runtime_error(std::string(operation) + " HRESULT=" + std::to_string(result));
}
struct Handle {
HANDLE value = nullptr;
Handle() = default;
Handle(const Handle&) = delete;
~Handle() { if (value) CloseHandle(value); }
};
struct ExternalApi {
HipApi& hip;
#define D4R_EXTERNAL(name) decltype(&::name) name = hip.library.symbol<decltype(&::name)>(#name)
D4R_EXTERNAL(hipImportExternalMemory);
D4R_EXTERNAL(hipExternalMemoryGetMappedBuffer);
D4R_EXTERNAL(hipDestroyExternalMemory);
D4R_EXTERNAL(hipImportExternalSemaphore);
D4R_EXTERNAL(hipDestroyExternalSemaphore);
D4R_EXTERNAL(hipWaitExternalSemaphoresAsync);
D4R_EXTERNAL(hipSignalExternalSemaphoresAsync);
#undef D4R_EXTERNAL
};
inline D3D12_RESOURCE_DESC resource_desc(ID3D12Resource* resource) {
#if defined(__MINGW32__)
D3D12_RESOURCE_DESC desc{}; resource->GetDesc(&desc); return desc;
#else
return resource->GetDesc();
#endif
}
inline LUID adapter_luid(ID3D12Device* device) {
#if defined(__MINGW32__)
LUID luid{}; device->GetAdapterLuid(&luid); return luid;
#else
return device->GetAdapterLuid();
#endif
}
inline ComPtr<ID3D12Resource> make_buffer(ID3D12Device* device, uint64_t bytes,
D3D12_HEAP_TYPE heapType, D3D12_HEAP_FLAGS flags, D3D12_RESOURCE_STATES state) {
D3D12_HEAP_PROPERTIES heap{}; heap.Type = heapType;
heap.CreationNodeMask = heap.VisibleNodeMask = 1;
D3D12_RESOURCE_DESC desc{};
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Width = bytes;
desc.Height = desc.DepthOrArraySize = desc.MipLevels = desc.SampleDesc.Count = 1;
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ComPtr<ID3D12Resource> resource;
dx(device->CreateCommittedResource(&heap, flags, &desc, state, nullptr,
IID_PPV_ARGS(resource.GetAddressOf())), "CreateCommittedResource(buffer)");
return resource;
}
inline void transition(ID3D12GraphicsCommandList* list, ID3D12Resource* resource,
D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) {
if (before == after) return;
D3D12_RESOURCE_BARRIER barrier{};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition = {resource, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, before, after};
list->ResourceBarrier(1, &barrier);
}
class SharedPlane {
ExternalApi& api_;
hipExternalMemory_t memory_ = nullptr;
void release() noexcept {
if (mapped) { (void)api_.hip.hipFree(mapped); mapped = nullptr; }
if (memory_) { (void)api_.hipDestroyExternalMemory(memory_); memory_ = nullptr; }
}
public:
ComPtr<ID3D12Resource> buffer;
D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint{};
UINT64 bytes = 0;
void* mapped = nullptr;
SharedPlane(ExternalApi& api, ID3D12Device* device, const D3D12_RESOURCE_DESC& texture) : api_(api) {
UINT rows = 0; UINT64 rowBytes = 0;
device->GetCopyableFootprints(&texture, 0, 1, 0, &footprint, &rows, &rowBytes, &bytes);
if (!bytes || bytes == UINT64_MAX || !footprint.Footprint.RowPitch)
throw std::runtime_error("Invalid shared texture footprint");
buffer = make_buffer(device, bytes, D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_FLAG_SHARED, D3D12_RESOURCE_STATE_COMMON);
const auto desc = resource_desc(buffer.Get());
D3D12_RESOURCE_ALLOCATION_INFO allocation{};
#if defined(__MINGW32__)
device->GetResourceAllocationInfo(&allocation, 0, 1, &desc);
#else
allocation = device->GetResourceAllocationInfo(0, 1, &desc);
#endif
Handle handle;
dx(device->CreateSharedHandle(buffer.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value), "CreateSharedHandle(VRAM)");
try {
hipExternalMemoryHandleDesc import{};
import.type = hipExternalMemoryHandleTypeD3D12Resource;
import.handle.win32.handle = handle.value;
import.size = allocation.SizeInBytes; import.flags = hipExternalMemoryDedicated;
api.hip.check(api.hipImportExternalMemory(&memory_, &import), "hipImportExternalMemory(D3D12)");
hipExternalMemoryBufferDesc map{}; map.size = bytes;
api.hip.check(api.hipExternalMemoryGetMappedBuffer(&mapped, memory_, &map), "hipExternalMemoryGetMappedBuffer");
} catch (...) { release(); throw; }
// Win32 imports retain their own reference; the exporter closes its HANDLE.
std::printf("D4R_IMPORT width=%u height=%u pitch=%u allocation=%llu cpu_copy=0\n",
footprint.Footprint.Width, footprint.Footprint.Height, footprint.Footprint.RowPitch, allocation.SizeInBytes);
}
SharedPlane(const SharedPlane&) = delete;
~SharedPlane() { release(); }
void copy_input(ID3D12GraphicsCommandList* list, ID3D12Resource* texture, D3D12_RESOURCE_STATES state) {
transition(list, texture, state, D3D12_RESOURCE_STATE_COPY_SOURCE);
transition(list, buffer.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);
D3D12_TEXTURE_COPY_LOCATION source{}, dest{};
source.pResource = texture; source.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dest.pResource = buffer.Get(); dest.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; dest.PlacedFootprint = footprint;
list->CopyTextureRegion(&dest, 0, 0, 0, &source, nullptr);
transition(list, buffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
transition(list, texture, D3D12_RESOURCE_STATE_COPY_SOURCE, state);
}
void copy_output(ID3D12GraphicsCommandList* list, ID3D12Resource* texture, D3D12_RESOURCE_STATES state) {
transition(list, texture, state, D3D12_RESOURCE_STATE_COPY_DEST);
transition(list, buffer.Get(), D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_SOURCE);
D3D12_TEXTURE_COPY_LOCATION source{}, dest{};
source.pResource = buffer.Get(); source.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; source.PlacedFootprint = footprint;
dest.pResource = texture; dest.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
list->CopyTextureRegion(&dest, 0, 0, 0, &source, nullptr);
transition(list, buffer.Get(), D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON);
transition(list, texture, D3D12_RESOURCE_STATE_COPY_DEST, state);
}
};
class SharedTimeline {
ExternalApi& api_;
hipExternalSemaphore_t semaphore_ = nullptr;
hipStream_t stream_ = nullptr;
Handle event_;
uint64_t value_ = 0;
public:
ComPtr<ID3D12Fence> fence;
SharedTimeline(ExternalApi& api, ID3D12Device* device) : api_(api) {
dx(device->CreateFence(0, D3D12_FENCE_FLAG_SHARED, IID_PPV_ARGS(fence.GetAddressOf())), "CreateFence(D3D12/HIP)");
Handle handle;
dx(device->CreateSharedHandle(fence.Get(), nullptr, GENERIC_ALL, nullptr, &handle.value), "CreateSharedHandle(fence)");
event_.value = CreateEventW(nullptr, FALSE, FALSE, nullptr);
if (!event_.value) throw std::runtime_error("CreateEvent(fence) failed");
hipExternalSemaphoreHandleDesc import{};
import.type = hipExternalSemaphoreHandleTypeD3D12Fence;
import.handle.win32.handle = handle.value;
api.hip.check(api.hipImportExternalSemaphore(&semaphore_, &import), "hipImportExternalSemaphore(D3D12Fence)");
try { api.hip.check(api.hip.hipStreamCreateWithFlags(&stream_, hipStreamNonBlocking), "hipStreamCreate(interop)"); }
catch (...) { (void)api.hipDestroyExternalSemaphore(semaphore_); semaphore_ = nullptr; throw; }
}
~SharedTimeline() {
if (stream_) { (void)api_.hip.hipStreamSynchronize(stream_); (void)api_.hip.hipStreamDestroy(stream_); }
if (semaphore_) (void)api_.hipDestroyExternalSemaphore(semaphore_);
}
// Caller must serialize transactions and submit the producer prefix first.
void wait_input(ID3D12CommandQueue* queue) {
const uint64_t ready = ++value_;
dx(queue->Signal(fence.Get(), ready), "Queue Signal(input)");
hipExternalSemaphoreWaitParams wait{}; wait.params.fence.value = ready;
api_.hip.check(api_.hipWaitExternalSemaphoresAsync(&semaphore_, &wait, 1, stream_), "HIP wait(D3D12 input)");
api_.hip.check(api_.hip.hipStreamSynchronize(stream_), "HIP input wait completion");
// Establish host-side completion as well: the Windows HIP semaphore
// wait currently needs a separately verified completion boundary before
// work is launched on CUDA's other streams. No image crosses the CPU.
if (fence->GetCompletedValue() < ready) {
dx(fence->SetEventOnCompletion(ready, event_.value), "SetEventOnCompletion(input)");
if (WaitForSingleObject(event_.value, 30000) != WAIT_OBJECT_0)
throw std::runtime_error("D3D12 input completion timeout");
}
}
void signal_output(ID3D12CommandQueue* queue) {
const uint64_t done = ++value_;
hipExternalSemaphoreSignalParams signal{}; signal.params.fence.value = done;
api_.hip.check(api_.hipSignalExternalSemaphoresAsync(&semaphore_, &signal, 1, stream_), "HIP signal(output)");
dx(queue->Wait(fence.Get(), done), "Queue Wait(HIP output)");
}
void drain(ID3D12CommandQueue* queue) {
const uint64_t done = ++value_;
dx(queue->Signal(fence.Get(), done), "Queue Signal(completion)");
if (fence->GetCompletedValue() < done) {
dx(fence->SetEventOnCompletion(done, event_.value), "SetEventOnCompletion");
if (WaitForSingleObject(event_.value, 30000) != WAIT_OBJECT_0)
throw std::runtime_error("D3D12 completion timeout; resources must remain alive");
}
}
};
}