#define COBJMACROS
#include <d3d12.h>
#include <stdint.h>
#include <stdlib.h>
#include "alpha2_native_gpu_d3d12.h"
#include "alpha2_native_gpu_status.h"
uint32_t alpha2_native_gpu_d3d12_wait_timeout(void);
#define ALPHA2_D3D12_ENCODE_COPY_SLOTS 4
#define ALPHA2_D3D12_WAIT_TIMED_OUT ((HRESULT)0x102)
typedef struct Alpha2D3D12EncodeSlot {
ID3D12CommandAllocator *allocator;
ID3D12GraphicsCommandList *commands;
ID3D12Fence *fence;
uint64_t fence_value;
int recording;
} Alpha2D3D12EncodeSlot;
typedef struct Alpha2D3D12EncodeCopier {
ID3D12Device *device;
Alpha2D3D12EncodeSlot slots[ALPHA2_D3D12_ENCODE_COPY_SLOTS];
uint32_t slot;
int unfenced;
} Alpha2D3D12EncodeCopier;
static void alpha2_d3d12_encode_slot_release(Alpha2D3D12EncodeSlot *slot) {
if (slot->commands != NULL) ID3D12GraphicsCommandList_Release(slot->commands);
if (slot->allocator != NULL) ID3D12CommandAllocator_Release(slot->allocator);
if (slot->fence != NULL) ID3D12Fence_Release(slot->fence);
slot->commands = NULL;
slot->allocator = NULL;
slot->fence = NULL;
}
static int32_t alpha2_d3d12_encode_slot_make_list(
ID3D12Device *device, Alpha2D3D12EncodeSlot *slot, HRESULT *result
) {
if (slot->allocator == NULL) {
*result = ID3D12Device_CreateCommandAllocator(
device, D3D12_COMMAND_LIST_TYPE_DIRECT,
&IID_ID3D12CommandAllocator, (void **)&slot->allocator);
if (FAILED(*result)) return ALPHA2_GPU_STEP_CREATE_COMMAND_ALLOCATOR;
}
*result = ID3D12Device_CreateCommandList(
device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, slot->allocator, NULL,
&IID_ID3D12GraphicsCommandList, (void **)&slot->commands);
if (FAILED(*result)) {
slot->commands = NULL;
return ALPHA2_GPU_STEP_CREATE_COMMAND_LIST;
}
*result = ID3D12GraphicsCommandList_Close(slot->commands);
if (FAILED(*result)) {
ID3D12GraphicsCommandList_Release(slot->commands);
slot->commands = NULL;
return ALPHA2_GPU_STEP_CLOSE_COMMAND_LIST;
}
return 0;
}
static int32_t alpha2_d3d12_encode_slot_wait(Alpha2D3D12EncodeSlot *slot, HRESULT *result) {
if (ID3D12Fence_GetCompletedValue(slot->fence) >= slot->fence_value) return 0;
HANDLE event = CreateEventW(NULL, FALSE, FALSE, NULL);
if (event == NULL) {
*result = 0;
return ALPHA2_GPU_STEP_HOST_ALLOCATION;
}
*result = ID3D12Fence_SetEventOnCompletion(slot->fence, slot->fence_value, event);
if (SUCCEEDED(*result))
WaitForSingleObject(event, (DWORD)alpha2_native_gpu_d3d12_wait_timeout());
CloseHandle(event);
if (FAILED(*result)) return ALPHA2_GPU_STEP_WAIT_FENCE;
if (ID3D12Fence_GetCompletedValue(slot->fence) < slot->fence_value) {
*result = ALPHA2_D3D12_WAIT_TIMED_OUT;
return ALPHA2_GPU_STEP_WAIT_FENCE;
}
return 0;
}
static int32_t alpha2_d3d12_encode_slot_ready(
Alpha2D3D12EncodeCopier *copier, Alpha2D3D12EncodeSlot *slot, HRESULT *result
) {
if (slot->recording) return 0;
int32_t step = alpha2_d3d12_encode_slot_wait(slot, result);
if (step != 0) return step;
*result = ID3D12CommandAllocator_Reset(slot->allocator);
if (FAILED(*result)) return ALPHA2_GPU_STEP_RESET_COMMAND_POOL;
if (slot->commands == NULL) {
step = alpha2_d3d12_encode_slot_make_list(copier->device, slot, result);
if (step != 0) return step;
}
*result = ID3D12GraphicsCommandList_Reset(slot->commands, slot->allocator, NULL);
if (FAILED(*result)) return ALPHA2_GPU_STEP_RESET_COMMAND_BUFFER;
slot->recording = 1;
return 0;
}
int32_t alpha2_native_gpu_d3d12_encode_copier_create(
void *raw_device, void **output, int32_t *status
) {
if (raw_device == NULL || output == NULL)
return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_ARGUMENTS, 0);
Alpha2D3D12EncodeCopier *copier = calloc(1, sizeof(*copier));
if (copier == NULL) return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_HOST_ALLOCATION, 0);
copier->device = raw_device;
ID3D12Device_AddRef(copier->device);
HRESULT result = S_OK;
int32_t step = 0;
for (uint32_t index = 0; index < ALPHA2_D3D12_ENCODE_COPY_SLOTS; ++index) {
Alpha2D3D12EncodeSlot *slot = &copier->slots[index];
step = alpha2_d3d12_encode_slot_make_list(copier->device, slot, &result);
if (step != 0) break;
result = ID3D12Device_CreateFence(
copier->device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, (void **)&slot->fence);
if (FAILED(result)) {
slot->fence = NULL;
step = ALPHA2_GPU_STEP_CREATE_FENCE;
break;
}
}
if (step != 0) {
for (uint32_t index = 0; index < ALPHA2_D3D12_ENCODE_COPY_SLOTS; ++index)
alpha2_d3d12_encode_slot_release(&copier->slots[index]);
ID3D12Device_Release(copier->device);
free(copier);
return ALPHA2_GPU_FAIL(status, step, result);
}
*output = copier;
return 0;
}
int32_t alpha2_native_gpu_d3d12_encode_copier_destroy(void *handle, int32_t *status) {
Alpha2D3D12EncodeCopier *copier = handle;
if (copier == NULL) return 0;
if (copier->unfenced) return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_QUEUE_SIGNAL, 0);
for (uint32_t index = 0; index < ALPHA2_D3D12_ENCODE_COPY_SLOTS; ++index) {
HRESULT result = S_OK;
int32_t step = alpha2_d3d12_encode_slot_wait(&copier->slots[index], &result);
if (step != 0) return ALPHA2_GPU_FAIL(status, step, result);
}
for (uint32_t index = 0; index < ALPHA2_D3D12_ENCODE_COPY_SLOTS; ++index)
alpha2_d3d12_encode_slot_release(&copier->slots[index]);
ID3D12Device_Release(copier->device);
free(copier);
return 0;
}
uint32_t alpha2_native_gpu_d3d12_encode_copier_slots(void) {
return ALPHA2_D3D12_ENCODE_COPY_SLOTS;
}
int32_t alpha2_native_gpu_d3d12_encode_copier_await_slot(
void *handle, uint32_t *slot, int32_t *status
) {
Alpha2D3D12EncodeCopier *copier = handle;
if (copier == NULL || slot == NULL)
return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_ARGUMENTS, 0);
if (copier->unfenced) return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_QUEUE_SIGNAL, 0);
HRESULT result = S_OK;
int32_t step = alpha2_d3d12_encode_slot_ready(copier, &copier->slots[copier->slot], &result);
if (step != 0) return ALPHA2_GPU_FAIL(status, step, result);
*slot = copier->slot;
return 0;
}
static int alpha2_d3d12_encode_holds(
ID3D12Resource *resource, DXGI_FORMAT format, uint32_t width, uint32_t height
) {
D3D12_RESOURCE_DESC descriptor;
resource->lpVtbl->GetDesc(resource, &descriptor);
return descriptor.Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D &&
descriptor.DepthOrArraySize == 1 && descriptor.Format == format &&
descriptor.Width >= width && descriptor.Height >= height;
}
static D3D12_RESOURCE_BARRIER alpha2_d3d12_encode_transition(
ID3D12Resource *resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after
) {
D3D12_RESOURCE_BARRIER barrier = {
.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION,
.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE,
};
barrier.Transition.pResource = resource;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = before;
barrier.Transition.StateAfter = after;
return barrier;
}
static void alpha2_d3d12_encode_copy_plane(
ID3D12GraphicsCommandList *commands, ID3D12Resource *target, uint32_t plane,
ID3D12Resource *source, uint32_t width, uint32_t height
) {
D3D12_TEXTURE_COPY_LOCATION to = {
.pResource = target,
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.SubresourceIndex = plane,
};
D3D12_TEXTURE_COPY_LOCATION from = {
.pResource = source,
.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX,
.SubresourceIndex = 0,
};
D3D12_BOX box = {.left = 0, .top = 0, .front = 0, .right = width, .bottom = height, .back = 1};
ID3D12GraphicsCommandList_CopyTextureRegion(commands, &to, 0, 0, 0, &from, &box);
}
int32_t alpha2_native_gpu_d3d12_encode_copier_submit(
void *handle, void *raw_queue, const Alpha2D3D12EncodeCopy *copy, uint64_t *signal_value,
int32_t *status
) {
Alpha2D3D12EncodeCopier *copier = handle;
if (copier == NULL || raw_queue == NULL || copy == NULL || signal_value == NULL ||
copy->luma == 0 || copy->chroma == 0 || copy->destination == 0 ||
copy->frame_fence == 0 || copy->width == 0 || copy->height == 0 ||
copy->width % 2 != 0 || copy->height % 2 != 0)
return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_ARGUMENTS, 0);
ID3D12CommandQueue *queue = raw_queue;
ID3D12Resource *luma = (ID3D12Resource *)(uintptr_t)copy->luma;
ID3D12Resource *chroma = (ID3D12Resource *)(uintptr_t)copy->chroma;
ID3D12Resource *destination = (ID3D12Resource *)(uintptr_t)copy->destination;
ID3D12Fence *frame_fence = (ID3D12Fence *)(uintptr_t)copy->frame_fence;
uint32_t width = copy->width, height = copy->height;
if (!alpha2_d3d12_encode_holds(luma, DXGI_FORMAT_R8_UNORM, width, height) ||
!alpha2_d3d12_encode_holds(chroma, DXGI_FORMAT_R8G8_UNORM, width / 2, height / 2) ||
!alpha2_d3d12_encode_holds(destination, DXGI_FORMAT_NV12, width, height))
return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_UNSUPPORTED, 0);
if (copier->unfenced) return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_QUEUE_SIGNAL, 0);
Alpha2D3D12EncodeSlot *slot = &copier->slots[copier->slot];
HRESULT result = S_OK;
int32_t step = alpha2_d3d12_encode_slot_ready(copier, slot, &result);
if (step != 0) return ALPHA2_GPU_FAIL(status, step, result);
ID3D12GraphicsCommandList *commands = slot->commands;
D3D12_RESOURCE_BARRIER enter[3] = {
alpha2_d3d12_encode_transition(
luma, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE),
alpha2_d3d12_encode_transition(
chroma, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE),
alpha2_d3d12_encode_transition(
destination, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST),
};
D3D12_RESOURCE_BARRIER leave[3] = {
alpha2_d3d12_encode_transition(
luma, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET),
alpha2_d3d12_encode_transition(
chroma, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_RENDER_TARGET),
alpha2_d3d12_encode_transition(
destination, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON),
};
ID3D12GraphicsCommandList_ResourceBarrier(commands, 3, enter);
alpha2_d3d12_encode_copy_plane(commands, destination, 0, luma, width, height);
alpha2_d3d12_encode_copy_plane(commands, destination, 1, chroma, width / 2, height / 2);
ID3D12GraphicsCommandList_ResourceBarrier(commands, 3, leave);
slot->recording = 0;
result = ID3D12GraphicsCommandList_Close(commands);
if (FAILED(result)) {
ID3D12GraphicsCommandList_Release(slot->commands);
slot->commands = NULL;
return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_CLOSE_COMMAND_LIST, result);
}
result = ID3D12CommandQueue_Wait(queue, frame_fence, copy->frame_value);
if (FAILED(result)) return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_QUEUE_WAIT, result);
ID3D12CommandList *lists[] = {(ID3D12CommandList *)commands};
ID3D12CommandQueue_ExecuteCommandLists(queue, 1, lists);
uint64_t value = slot->fence_value + 1;
slot->fence_value = value;
result = ID3D12CommandQueue_Signal(queue, slot->fence, value);
if (FAILED(result)) {
copier->unfenced = 1;
return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_QUEUE_SIGNAL, result);
}
copier->slot = (copier->slot + 1) % ALPHA2_D3D12_ENCODE_COPY_SLOTS;
result = ID3D12CommandQueue_Signal(queue, frame_fence, copy->frame_value + 1);
if (FAILED(result)) return ALPHA2_GPU_FAIL(status, ALPHA2_GPU_STEP_QUEUE_SIGNAL, result);
*signal_value = copy->frame_value + 1;
return 0;
}