[PATCH 0/2] MR11420: mfplat: Implement d3d12-backed buffers.
Follow-up to !9777 -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420
From: Charlotte Pabst <cpabst@codeweavers.com> --- dlls/mfplat/tests/mfplat.c | 854 ++++++++++++++++++++++++++++++++++++- 1 file changed, 850 insertions(+), 4 deletions(-) diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 1e6fa0639c3..c81f8738d01 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -516,6 +516,214 @@ static void put_d3d11_texture_color(ID3D11Texture2D *texture, unsigned int x, un release_d3d11_resource_readback(&rb, TRUE); } +struct d3d12_resource_readback +{ + BOOL upload; + ID3D12Resource *resource; + ID3D12Resource *parent_resource; + IMFD3D12SynchronizationObjectCommands *sync; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint; + unsigned int sub_resource_idx; + void *data; +}; + +static HRESULT copy_d3d12_resource_readback(struct d3d12_resource_readback *rb) +{ + D3D12_COMMAND_QUEUE_DESC queue_desc = { .Type = D3D12_COMMAND_LIST_TYPE_COPY }; + D3D12_RESOURCE_BARRIER pre_barrier, post_barrier; + D3D12_TEXTURE_COPY_LOCATION res_loc, rb_loc; + ID3D12Device *device = NULL; + ID3D12CommandAllocator *allocator = NULL; + ID3D12GraphicsCommandList *list = NULL; + ID3D12CommandQueue *queue = NULL; + ID3D12Fence *fence = NULL; + HRESULT hr; + + hr = ID3D12Resource_GetDevice(rb->resource, &IID_ID3D12Device, (void **) &device); + if (FAILED(hr)) goto end; + + hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COPY, + &IID_ID3D12CommandAllocator, (void **) &allocator); + if (FAILED(hr)) goto end; + + hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_COPY, allocator, NULL, + &IID_ID3D12GraphicsCommandList, (void **) &list); + if (FAILED(hr)) goto end; + + hr = ID3D12Device_CreateCommandQueue(device, &queue_desc, &IID_ID3D12CommandQueue, (void **) &queue); + if (FAILED(hr)) goto end; + + hr = ID3D12Device_CreateFence(device, 0, 0, &IID_ID3D12Fence, (void **) &fence); + if (FAILED(hr)) goto end; + + res_loc.pResource = rb->parent_resource; + res_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + res_loc.SubresourceIndex = rb->sub_resource_idx; + rb_loc.pResource = rb->resource; + rb_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + rb_loc.PlacedFootprint = rb->footprint; + + pre_barrier.Type = post_barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + pre_barrier.Flags = post_barrier.Flags = 0; + pre_barrier.Transition.pResource = post_barrier.Transition.pResource = rb->parent_resource; + pre_barrier.Transition.Subresource = post_barrier.Transition.Subresource = rb->sub_resource_idx; + pre_barrier.Transition.StateBefore = post_barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON; + pre_barrier.Transition.StateAfter = post_barrier.Transition.StateBefore = rb->upload ? D3D12_RESOURCE_STATE_COPY_DEST : D3D12_RESOURCE_STATE_COPY_SOURCE; + + ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &pre_barrier); + ID3D12GraphicsCommandList_CopyTextureRegion(list, rb->upload ? &res_loc : &rb_loc, 0, 0, 0, rb->upload ? &rb_loc : &res_loc, NULL); + ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &post_barrier); + ID3D12GraphicsCommandList_Close(list); + + if (!rb->upload) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReadyWait(rb->sync, queue); + + ID3D12CommandQueue_ExecuteCommandLists(queue, 1, (ID3D12CommandList **) &list); + + if (rb->upload) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReady(rb->sync, queue); + + ID3D12CommandQueue_Signal(queue, fence, 1); + ID3D12Fence_SetEventOnCompletion(fence, 1, NULL); + + hr = S_OK; + +end: + if (device) ID3D12Device_Release(device); + if (allocator) ID3D12CommandAllocator_Release(allocator); + if (list) ID3D12GraphicsCommandList_Release(list); + if (queue) ID3D12CommandQueue_Release(queue); + if (fence) ID3D12Fence_Release(fence); + + return hr; +} + +static HRESULT get_d3d12_resource_readback(ID3D12Resource *resource, IMFD3D12SynchronizationObjectCommands *sync, + unsigned int sub_resource_idx, struct d3d12_resource_readback *rb, BOOL upload) +{ + D3D12_HEAP_PROPERTIES heap_prop = { .Type = upload ? D3D12_HEAP_TYPE_UPLOAD : D3D12_HEAP_TYPE_READBACK }; + D3D12_RESOURCE_DESC desc; + D3D12_RANGE empty_range = { 0, 0 }; + UINT64 bytes; + ID3D12Device *device = NULL; + HRESULT hr; + + memset(rb, 0, sizeof(*rb)); + + rb->upload = upload; + rb->parent_resource = resource; + rb->sub_resource_idx = sub_resource_idx; + rb->sync = sync; + + hr = ID3D12Resource_GetDevice(rb->parent_resource, &IID_ID3D12Device, (void **) &device); + if (FAILED(hr)) goto end; + + desc = ID3D12Resource_GetDesc(rb->parent_resource); + ID3D12Device_GetCopyableFootprints(device, &desc, rb->sub_resource_idx, 1, 0, &rb->footprint, NULL, NULL, &bytes); + rb->footprint.Offset = 0; + + memset(&desc, 0, sizeof(desc)); + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Alignment = 0; + desc.Width = bytes; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = 0; + + hr = ID3D12Device_CreateCommittedResource(device, &heap_prop, D3D12_HEAP_FLAG_NONE, &desc, + rb->upload ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST, NULL, + &IID_ID3D12Resource, (void **) &rb->resource); + if (FAILED(hr)) goto end; + + if (!rb->upload) + { + hr = copy_d3d12_resource_readback(rb); + if (FAILED(hr)) goto end; + } + + hr = ID3D12Resource_Map(rb->resource, 0, rb->upload ? &empty_range : NULL, &rb->data); + +end: + if (FAILED(hr) && rb->resource) + { + ID3D12Resource_Release(rb->resource); + rb->resource = NULL; + } + if (device) ID3D12Device_Release(device); + return hr; +} + +static void release_d3d12_resource_readback(struct d3d12_resource_readback *rb) +{ + D3D12_RANGE empty_range = { 0, 0 }; + + if (rb->resource) + { + ID3D12Resource_Unmap(rb->resource, 0, rb->upload ? NULL : &empty_range); + if (rb->upload) + copy_d3d12_resource_readback(rb); + ID3D12Resource_Release(rb->resource); + } +} + +static void *get_d3d12_readback_data(struct d3d12_resource_readback *rb, + unsigned int x, unsigned int y, unsigned int z, unsigned byte_width) +{ + return (BYTE *)rb->data + rb->footprint.Offset + (z * rb->footprint.Footprint.Height + y) * rb->footprint.Footprint.RowPitch + x * byte_width; +} + +static DWORD get_d3d12_readback_u32(struct d3d12_resource_readback *rb, unsigned int x, unsigned int y, unsigned int z) +{ + return *(DWORD *)get_d3d12_readback_data(rb, x, y, z, sizeof(DWORD)); +} + +static DWORD get_d3d12_readback_color(struct d3d12_resource_readback *rb, unsigned int x, unsigned int y, unsigned int z) +{ + return get_d3d12_readback_u32(rb, x, y, z); +} + +static DWORD get_d3d12_texture_color(ID3D12Resource *resource, IMFD3D12SynchronizationObjectCommands *sync, unsigned int sub_resource_idx, unsigned int x, unsigned int y) +{ + struct d3d12_resource_readback rb; + DWORD color; + HRESULT hr; + + hr = get_d3d12_resource_readback(resource, sync, sub_resource_idx, &rb, FALSE); + ok(SUCCEEDED(hr), "unexpected hr: %#08lx\n", hr); + color = get_d3d12_readback_color(&rb, x, y, 0); + release_d3d12_resource_readback(&rb); + + return color; +} + +static void put_d3d12_readback_u32(struct d3d12_resource_readback *rb, + unsigned int x, unsigned int y, unsigned int z, DWORD color) +{ + *(DWORD *)get_d3d12_readback_data(rb, x, y, z, sizeof(DWORD)) = color; +} + +static void put_d3d12_readback_color(struct d3d12_resource_readback *rb, + unsigned int x, unsigned int y, unsigned int z, DWORD color) +{ + put_d3d12_readback_u32(rb, x, y, z, color); +} + +static void put_d3d12_texture_color(ID3D12Resource *resource, IMFD3D12SynchronizationObjectCommands *sync, unsigned int sub_resource_idx, unsigned int x, unsigned int y, DWORD color) +{ + struct d3d12_resource_readback rb; + HRESULT hr; + + hr = get_d3d12_resource_readback(resource, sync, sub_resource_idx, &rb, TRUE); + ok(SUCCEEDED(hr), "unexpected hr: %#08lx\n", hr); + put_d3d12_readback_color(&rb, x, y, 0, color); + release_d3d12_resource_readback(&rb); +} + static HRESULT (WINAPI *pD3D11CreateDevice)(IDXGIAdapter *adapter, D3D_DRIVER_TYPE driver_type, HMODULE swrast, UINT flags, const D3D_FEATURE_LEVEL *feature_levels, UINT levels, UINT sdk_version, ID3D11Device **device_out, D3D_FEATURE_LEVEL *obtained_feature_level, ID3D11DeviceContext **immediate_context); @@ -11462,6 +11670,51 @@ static void test_d3d11_surface_buffer(void) ID3D11Device_Release(device); } +enum test_d3d12_buffer_lock_kind +{ + TEST_D3D12_BUFFER_LOCK = 0, + TEST_D3D12_BUFFER_LOCK2D, + TEST_D3D12_BUFFER_LOCK2DSIZE_READ, + TEST_D3D12_BUFFER_LOCK2DSIZE_READWRITE, + TEST_D3D12_BUFFER_LOCK_COUNT, +}; + +struct test_d3d12_buffer_lock_param +{ + IMFMediaBuffer *buffer; + IMF2DBuffer2 *_2dbuffer2; + enum test_d3d12_buffer_lock_kind kind; +}; + +static DWORD CALLBACK test_d3d12_buffer_lock_thread(void *arg) +{ + struct test_d3d12_buffer_lock_param *param = arg; + BYTE *scanline0, *start; + LONG pitch; + DWORD max_length, cur_length; + HRESULT hr = S_OK; + + switch (param->kind) + { + case TEST_D3D12_BUFFER_LOCK: + hr = IMFMediaBuffer_Lock(param->buffer, &scanline0, &max_length, &cur_length); + break; + case TEST_D3D12_BUFFER_LOCK2D: + hr = IMF2DBuffer2_Lock2D(param->_2dbuffer2, &scanline0, &pitch); + break; + case TEST_D3D12_BUFFER_LOCK2DSIZE_READ: + hr = IMF2DBuffer2_Lock2DSize(param->_2dbuffer2, MF2DBuffer_LockFlags_Read, &scanline0, &pitch, &start, &max_length); + break; + case TEST_D3D12_BUFFER_LOCK2DSIZE_READWRITE: + hr = IMF2DBuffer2_Lock2DSize(param->_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &scanline0, &pitch, &start, &max_length); + break; + default: break; + } + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + return 0; +} + static void test_d3d12_surface_buffer(void) { IMFDXGIBuffer *dxgi_buffer; @@ -11469,9 +11722,24 @@ static void test_d3d12_surface_buffer(void) D3D12_RESOURCE_DESC desc; ID3D12Resource *resource; IMFMediaBuffer *buffer; + IMF2DBuffer *_2d_buffer; + IMF2DBuffer2 *_2dbuffer2; unsigned int refcount; ID3D12Device *device; IUnknown *obj; + IMFD3D12SynchronizationObject *sync_obj; + IMFD3D12SynchronizationObjectCommands *sync_cmd; + ID3D12CommandQueue *queue; + D3D12_COMMAND_QUEUE_DESC queue_desc = { .Type = D3D12_COMMAND_LIST_TYPE_COPY }; + struct test_d3d12_buffer_lock_param buffer_lock_param; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; + DWORD max_length, cur_length, length, color; + BYTE *data, *data2, *buffer_start; + LONG pitch, pitch2; + UINT index; + UINT64 total_bytes; + HANDLE event, thread; + DWORD status; HRESULT hr; /* d3d12 */ @@ -11481,6 +11749,9 @@ static void test_d3d12_surface_buffer(void) return; } + hr = MFStartup(MF_VERSION, MFSTARTUP_FULL); + ok(hr == S_OK, "Failed to start up, hr %#lx.\n", hr); + memset(&heap_props, 0, sizeof(heap_props)); heap_props.Type = D3D12_HEAP_TYPE_DEFAULT; @@ -11497,6 +11768,8 @@ static void test_d3d12_surface_buffer(void) desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + ID3D12Device_GetCopyableFootprints(device, &desc, 0, 1, 0, &layout, NULL, NULL, &total_bytes); + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_RENDER_TARGET, NULL, &IID_ID3D12Resource, (void **)&resource); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -11506,6 +11779,7 @@ static void test_d3d12_surface_buffer(void) { todo_wine win_skip("D3D12 resource buffers are not supported.\n"); + ID3D12Resource_Release(resource); goto notsupported; } ok(hr == S_OK, "Failed to create a buffer, hr %#lx.\n", hr); @@ -11517,20 +11791,592 @@ if (SUCCEEDED(hr)) check_interface(buffer, &IID_IMFDXGIBuffer, TRUE); check_interface(buffer, &IID_IMFGetService, FALSE); + max_length = 0; + hr = IMFMediaBuffer_GetMaxLength(buffer, &max_length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(max_length == total_bytes, "Unexpected length %lu.\n", max_length); + + hr = IMFMediaBuffer_GetCurrentLength(buffer, &cur_length); + ok(hr == S_OK, "Failed to get length, hr %#lx.\n", hr); + ok(!cur_length, "Unexpected length %lu.\n", cur_length); + + hr = IMFMediaBuffer_SetCurrentLength(buffer, 4096); + ok(hr == S_OK, "Failed to set length, hr %#lx.\n", hr); + + hr = IMFMediaBuffer_GetCurrentLength(buffer, &cur_length); + ok(hr == S_OK, "Failed to get length, hr %#lx.\n", hr); + ok(!cur_length, "Unexpected length %lu.\n", cur_length); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer, (void **)&_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_GetContiguousLength(_2d_buffer, NULL); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_GetContiguousLength(_2d_buffer, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(length == desc.Height * desc.Width * 4, "Unexpected length %lu.\n", length); + IMF2DBuffer_Release(_2d_buffer); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMFDXGIBuffer, (void **)&dxgi_buffer); ok(hr == S_OK, "Failed to get interface, hr %#lx.\n", hr); + EXPECT_REF(resource, 2); hr = IMFDXGIBuffer_GetResource(dxgi_buffer, &IID_ID3D12Resource, (void **)&obj); ok(hr == S_OK, "Failed to get resource, hr %#lx.\n", hr); + EXPECT_REF(resource, 3); ok(obj == (IUnknown *)resource, "Unexpected resource pointer.\n"); IUnknown_Release(obj); + hr = IMFDXGIBuffer_GetSubresourceIndex(dxgi_buffer, NULL); + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + hr = IMFDXGIBuffer_GetSubresourceIndex(dxgi_buffer, &index); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(index == 0, "Unexpected subresource index.\n"); + + hr = IMFDXGIBuffer_SetUnknown(dxgi_buffer, &IID_IMFDXGIBuffer, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFDXGIBuffer_SetUnknown(dxgi_buffer, &IID_IMFDXGIBuffer, (void *)device); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFDXGIBuffer_SetUnknown(dxgi_buffer, &IID_IMFDXGIBuffer, (void *)device); + ok(hr == HRESULT_FROM_WIN32(ERROR_OBJECT_ALREADY_EXISTS), "Unexpected hr %#lx.\n", hr); + + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &IID_IMFDXGIBuffer, &IID_ID3D12Device, (void **)&obj); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(obj == (IUnknown *)device, "Unexpected pointer.\n"); + IUnknown_Release(obj); + + hr = IMFDXGIBuffer_SetUnknown(dxgi_buffer, &IID_IMFDXGIBuffer, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &IID_IMFDXGIBuffer, &IID_IUnknown, (void **)&obj); + ok(hr == MF_E_NOT_FOUND, "Unexpected hr %#lx.\n", hr); + + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &MF_D3D12_SYNCHRONIZATION_OBJECT, &IID_IMFD3D12SynchronizationObject, (void **) &sync_obj); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFD3D12SynchronizationObject_Release(sync_obj); + + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &MF_D3D12_SYNCHRONIZATION_OBJECT, &IID_IMFD3D12SynchronizationObjectCommands, (void **) &sync_cmd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFDXGIBuffer_Release(dxgi_buffer); - IMFMediaBuffer_Release(buffer); -} -notsupported: - ID3D12Resource_Release(resource); + hr = ID3D12Device_CreateCommandQueue(device, &queue_desc, &IID_ID3D12CommandQueue, (void **) &queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFD3D12SynchronizationObjectCommands_EnqueueResourceReady(sync_cmd, queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock() is readonly */ + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + max_length = cur_length = 0; + data = NULL; + hr = IMFMediaBuffer_Lock(buffer, &data, &max_length, &cur_length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(max_length && max_length == cur_length, "Unexpected length %lu.\n", max_length); + if (data) *(DWORD *)data = ~0u; + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMFMediaBuffer_Lock(buffer, &data, &max_length, &cur_length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!*(DWORD *)data, "Unexpected buffer %#lx.\n", *(DWORD *)data); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2DSize(Write) -> Unlock2D() success */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2DSize(ReadWrite) -> Unlock2D() failure */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + /* Lock2DSize(Write) -> Unlock2D() now fails */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + put_d3d12_texture_color(resource, sync_cmd, 0, 1, 0, 0xcdcdcdcd); + + /* Lock2DSize(Read) -> Unlock2D() success */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Recent write was not picked up */ + ok(!((DWORD *)data)[1], "Unexpected dword %#lx.\n", ((DWORD *)data)[1]); + + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2DSize(Write) -> Unlock2D() succeeds again after read */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2D() is readonly, Unlock2D() fails */ + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + if (data) *(DWORD *)data = ~0u; + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + /* Lock2DSize() with ReadWrite is readonly, Unlock2D() fails */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + if (data) *(DWORD *)data = ~0u; + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Texture updates. */ + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(length == layout.Footprint.Height * layout.Footprint.RowPitch, "Unexpected length %lu.\n", length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (data) *(DWORD *)data = ~0u; + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(!color, "Unexpected texture color %#lx.\n", color); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(color == ~0u, "Unexpected texture color %#lx.\n", color); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(*(DWORD *)data == ~0u, "Unexpected buffer %#lx.\n", *(DWORD *)data); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2D()/Unlock2D() */ + hr = IMF2DBuffer_GetScanline0AndPitch(_2d_buffer, &data2, &pitch2); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!data && pitch == layout.Footprint.RowPitch, "Unexpected pitch %ld.\n", pitch); + + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!data && pitch == layout.Footprint.RowPitch, "Unexpected pitch %ld.\n", pitch); + + hr = IMF2DBuffer_GetScanline0AndPitch(_2d_buffer, &data2, &pitch2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data2 == data && pitch2 == pitch, "Unexpected data/pitch.\n"); + + hr = IMFMediaBuffer_Lock(buffer, &data, &max_length, &cur_length); + ok(hr == MF_E_INVALIDREQUEST, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(buffer, &data, NULL, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == MF_E_UNEXPECTED, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer_Release(_2d_buffer); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock flags are honored, so reads and writes are discarded if + * the flags are not correct. Also, previous content is discarded + * when locking for writing and not for reading. */ + put_d3d12_texture_color(resource, sync_cmd, 0, 0, 0, 0xcdcdcdcd); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data == data2, "Unexpected scanline pointer.\n"); + ok(*(DWORD *)data == 0xcdcdcdcd, "Unexpected leading dword %#lx.\n", *(DWORD *)data); + memset(data, 0xab, 4); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(color == 0xcdcdcdcd, "Unexpected leading dword %#lx.\n", color); + put_d3d12_texture_color(resource, sync_cmd, 0, 0, 0, 0xefefefef); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(*(DWORD *)data != 0xefefefef, "Unexpected leading dword.\n"); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(color != 0xefefefef, "Unexpected leading dword.\n"); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(*(DWORD *)data != 0xefefefef, "Unexpected leading dword.\n"); + memset(data, 0x89, 4); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(color == 0x89898989, "Unexpected leading dword %#lx.\n", color); + + /* When relocking for writing, stores are not committed if they + * were issued before relocking. */ + put_d3d12_texture_color(resource, sync_cmd, 0, 0, 0, 0xcdcdcdcd); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + memset(data, 0xab, 4); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(color == 0xcdcdcdcd, "Unexpected leading dword %#lx.\n", color); + + /* When relocking for writing, stores are not committed if they + * were issued after relocking. */ + put_d3d12_texture_color(resource, sync_cmd, 0, 0, 0, 0xcdcdcdcd); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + memset(data, 0xab, 4); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + IMF2DBuffer2_Unlock2D(_2dbuffer2); + + color = get_d3d12_texture_color(resource, sync_cmd, 0, 0, 0); + ok(color == 0xcdcdcdcd, "Unexpected leading dword %#lx.\n", color); + + /* Flags incompatibilities. */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Except when originally locking for writing. */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_LOCKED), "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer2_Release(_2dbuffer2); + IMFD3D12SynchronizationObjectCommands_Release(sync_cmd); + IMFMediaBuffer_Release(buffer); + + /* Read blocks on ResourceReady, issues ResourceRelease */ + event = CreateEventA(NULL, FALSE, FALSE, NULL); + for (enum test_d3d12_buffer_lock_kind kind = 0; kind < TEST_D3D12_BUFFER_LOCK_COUNT; kind++) + { + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMFDXGIBuffer, (void **) &dxgi_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **) &_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &MF_D3D12_SYNCHRONIZATION_OBJECT, + &IID_IMFD3D12SynchronizationObject, (void **)&sync_obj); + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &MF_D3D12_SYNCHRONIZATION_OBJECT, + &IID_IMFD3D12SynchronizationObjectCommands, (void **)&sync_cmd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + buffer_lock_param.buffer = buffer; + buffer_lock_param._2dbuffer2 = _2dbuffer2; + buffer_lock_param.kind = kind; + + thread = CreateThread(NULL, 0, test_d3d12_buffer_lock_thread, &buffer_lock_param, 0, NULL); + + status = WaitForSingleObject(thread, 100); + ok(status == WAIT_TIMEOUT, "got %#lx.\n", status); + hr = IMFD3D12SynchronizationObject_SignalEventOnFinalResourceRelease(sync_obj, event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_TIMEOUT, "got %#lx.\n", status); + + hr = IMFD3D12SynchronizationObjectCommands_EnqueueResourceReady(sync_cmd, queue); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(thread, 100); + ok(status == WAIT_OBJECT_0, "got %#lx.\n", status); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_OBJECT_0, "got %#lx.\n", status); + + CloseHandle(thread); + + if (kind == TEST_D3D12_BUFFER_LOCK) + hr = IMFMediaBuffer_Unlock(buffer); + else + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + + if (kind == TEST_D3D12_BUFFER_LOCK2D || kind == TEST_D3D12_BUFFER_LOCK2DSIZE_READWRITE) + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + else + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMFD3D12SynchronizationObject_Release(sync_obj); + IMFD3D12SynchronizationObjectCommands_Release(sync_cmd); + IMFDXGIBuffer_Release(dxgi_buffer); + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + } + + /* Write signals ResourceReady */ + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **) &_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMFDXGIBuffer, (void **) &dxgi_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFDXGIBuffer_GetUnknown(dxgi_buffer, &MF_D3D12_SYNCHRONIZATION_OBJECT, + &IID_IMFD3D12SynchronizationObjectCommands, (void **)&sync_cmd); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMFD3D12SynchronizationObjectCommands_SignalEventOnResourceReady(sync_cmd, event); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_TIMEOUT, "got %#lx.\n", status); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &buffer_start, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_TIMEOUT, "got %#lx.\n", status); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_OBJECT_0, "got %#lx.\n", status); + IMFD3D12SynchronizationObjectCommands_Release(sync_cmd); + IMFDXGIBuffer_Release(dxgi_buffer); + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + + /* Bottom up. */ + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, TRUE, &buffer); + ok(hr == S_OK, "Failed to create a buffer, hr %#lx.\n", hr); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(!!data && pitch == layout.Footprint.RowPitch, "Unexpected pitch %ld.\n", pitch); + + hr = IMF2DBuffer2_GetScanline0AndPitch(_2dbuffer2, &data2, &pitch2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(data2 == data && pitch2 == pitch, "Unexpected data/pitch.\n"); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + + ID3D12Resource_Release(resource); + + /* creation tests */ + + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_RENDER_TARGET, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + + desc.Flags = 0; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_COMMON, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + + /* Subresource index 1. */ + desc.MipLevels = 0; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_RENDER_TARGET, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 1, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Pitch reflects top level. */ + put_d3d12_texture_color(resource, sync_cmd, 1, 0, 0, 0xff00ff00); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Read, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(*(DWORD *)data == 0xff00ff00, "Unexpected color %#lx.\n", *(DWORD *)data); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_NV12; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_RENDER_TARGET, NULL, &IID_ID3D12Resource, (void **)&resource); + if (SUCCEEDED(hr)) + { + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "got %#lx.\n", hr); + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); + ok(hr == S_OK, "got %#lx.\n", hr); + + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &buffer_start, &length); + ok(hr == S_OK, "got %#lx.\n", hr); + + ok(pitch >= desc.Width, "got %ld.\n", pitch); + ok(length == pitch * desc.Height * 3 / 2, "got %lu.\n", length); + + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + } + else + { + skip("Failed to create NV12 texture, hr %#lx, skipping test.\n", hr); + } + + heap_props.Type = D3D12_HEAP_TYPE_READBACK; + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Width = 32*32; + desc.Height = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = 0; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_COPY_DEST, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == MF_E_INVALIDMEDIATYPE, "Unexpected hr %#lx.\n", hr); + ID3D12Resource_Release(resource); + + heap_props.Type = D3D12_HEAP_TYPE_UPLOAD; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == MF_E_INVALIDMEDIATYPE, "Unexpected hr %#lx.\n", hr); + ID3D12Resource_Release(resource); + + CloseHandle(event); + ID3D12CommandQueue_Release(queue); +} + +notsupported: + hr = MFShutdown(); + ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr); + refcount = ID3D12Device_Release(device); ok(!refcount, "Unexpected device refcount %u.\n", refcount); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11420
From: Charlotte Pabst <cpabst@codeweavers.com> --- dlls/mfplat/buffer.c | 556 ++++++++++++++++++++++++++++++++++++- dlls/mfplat/tests/mfplat.c | 1 - 2 files changed, 553 insertions(+), 4 deletions(-) diff --git a/dlls/mfplat/buffer.c b/dlls/mfplat/buffer.c index 6d59245b40c..d2d9c42c4c2 100644 --- a/dlls/mfplat/buffer.c +++ b/dlls/mfplat/buffer.c @@ -28,6 +28,12 @@ #include "d3d9.h" #include "evr.h" +#undef INITGUID +#undef EXTERN_GUID +#define EXTERN_GUID DEFINE_GUID +#include <guiddef.h> +#include "mfd3d12.h" + WINE_DEFAULT_DEBUG_CHANNEL(mfplat); #define ALIGN_SIZE(size, alignment) (((size) + (alignment)) & ~((alignment))) @@ -72,6 +78,19 @@ struct buffer D3D11_MAPPED_SUBRESOURCE map_desc; struct attributes attributes; } dxgi_surface; + struct + { + BOOL keep_transfer; + ID3D12Resource *resource, *upload, *readback, *transfer; + ID3D12CommandAllocator *allocator; + ID3D12CommandQueue *queue; + ID3D12Fence *fence; + UINT64 fence_value; + IMFD3D12SynchronizationObject *sync_obj; + IMFD3D12SynchronizationObjectCommands *sync_cmd; + void *data; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; + } d3d12_surface; CRITICAL_SECTION cs; }; @@ -171,6 +190,18 @@ static ULONG WINAPI memory_buffer_Release(IMFMediaBuffer *iface) ID3D11Texture2D_Release(buffer->dxgi_surface.rb_texture); clear_attributes_object(&buffer->dxgi_surface.attributes); } + if (buffer->d3d12_surface.resource) + { + ID3D12Resource_Release(buffer->d3d12_surface.resource); + if (buffer->d3d12_surface.readback) ID3D12Resource_Release(buffer->d3d12_surface.readback); + if (buffer->d3d12_surface.upload) ID3D12Resource_Release(buffer->d3d12_surface.upload); + if (buffer->d3d12_surface.allocator) ID3D12CommandAllocator_Release(buffer->d3d12_surface.allocator); + if (buffer->d3d12_surface.queue) ID3D12CommandQueue_Release(buffer->d3d12_surface.queue); + if (buffer->d3d12_surface.fence) ID3D12Fence_Release(buffer->d3d12_surface.fence); + if (buffer->d3d12_surface.sync_obj) IMFD3D12SynchronizationObject_Release(buffer->d3d12_surface.sync_obj); + if (buffer->d3d12_surface.sync_cmd) IMFD3D12SynchronizationObjectCommands_Release(buffer->d3d12_surface.sync_cmd); + clear_attributes_object(&buffer->dxgi_surface.attributes); + } DeleteCriticalSection(&buffer->cs); free(buffer->_2d.linear_buffer); _aligned_free(buffer->data); @@ -1332,6 +1363,428 @@ static const IMFDXGIBufferVtbl dxgi_buffer_vtbl = dxgi_buffer_SetUnknown, }; +static HRESULT d3d12_surface_buffer_copy_transfer_resource(struct buffer *buffer, MF2DBuffer_LockFlags flags) +{ + ID3D12Device *device; + ID3D12GraphicsCommandList *list = NULL; + D3D12_RESOURCE_BARRIER pre_barrier, post_barrier; + D3D12_TEXTURE_COPY_LOCATION res_loc, tx_loc; + HRESULT hr; + + hr = ID3D12Resource_GetDevice(buffer->d3d12_surface.resource, &IID_ID3D12Device, (void **) &device); + if (FAILED(hr)) + return hr; + + if (!buffer->d3d12_surface.queue) + { + D3D12_COMMAND_QUEUE_DESC queue_desc = { .Type = D3D12_COMMAND_LIST_TYPE_COPY }; + hr = ID3D12Device_CreateCommandQueue(device, &queue_desc, + &IID_ID3D12CommandQueue, (void **) &buffer->d3d12_surface.queue); + if (FAILED(hr)) + goto end; + } + + if (!buffer->d3d12_surface.allocator) + { + hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COPY, + &IID_ID3D12CommandAllocator, (void **) &buffer->d3d12_surface.allocator); + if (FAILED(hr)) + goto end; + } + + if (!buffer->d3d12_surface.fence) + { + hr = ID3D12Device_CreateFence(device, 0, 0, &IID_ID3D12Fence, (void **) &buffer->d3d12_surface.fence); + if (FAILED(hr)) + goto end; + } + + hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_COPY, buffer->d3d12_surface.allocator, NULL, + &IID_ID3D12GraphicsCommandList, (void **) &list); + if (FAILED(hr)) + goto end; + + res_loc.pResource = buffer->d3d12_surface.resource; + res_loc.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX; + res_loc.SubresourceIndex = buffer->dxgi_surface.sub_resource_idx; + tx_loc.pResource = buffer->d3d12_surface.transfer; + tx_loc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT; + tx_loc.PlacedFootprint = buffer->d3d12_surface.layout; + + pre_barrier.Type = post_barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; + pre_barrier.Flags = post_barrier.Flags = 0; + pre_barrier.Transition.pResource = post_barrier.Transition.pResource = buffer->d3d12_surface.resource; + pre_barrier.Transition.Subresource = post_barrier.Transition.Subresource = buffer->dxgi_surface.sub_resource_idx; + pre_barrier.Transition.StateBefore = post_barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COMMON; + pre_barrier.Transition.StateAfter = post_barrier.Transition.StateBefore = + flags == MF2DBuffer_LockFlags_Write ? D3D12_RESOURCE_STATE_COPY_DEST : D3D12_RESOURCE_STATE_COPY_SOURCE; + + ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &pre_barrier); + ID3D12GraphicsCommandList_CopyTextureRegion(list, + flags == MF2DBuffer_LockFlags_Write ? &res_loc : &tx_loc, 0, 0, 0, + flags == MF2DBuffer_LockFlags_Write ? &tx_loc : &res_loc, NULL); + ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &post_barrier); + hr = ID3D12GraphicsCommandList_Close(list); + if (FAILED(hr)) + goto end; + + if (flags != MF2DBuffer_LockFlags_Write) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReadyWait(buffer->d3d12_surface.sync_cmd, buffer->d3d12_surface.queue); + + ID3D12CommandQueue_ExecuteCommandLists(buffer->d3d12_surface.queue, 1, (ID3D12CommandList **) &list); + + if (flags == MF2DBuffer_LockFlags_Write) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReady(buffer->d3d12_surface.sync_cmd, buffer->d3d12_surface.queue); + else + IMFD3D12SynchronizationObjectCommands_EnqueueResourceRelease(buffer->d3d12_surface.sync_cmd, buffer->d3d12_surface.queue); + + buffer->d3d12_surface.fence_value++; + ID3D12CommandQueue_Signal(buffer->d3d12_surface.queue, buffer->d3d12_surface.fence, buffer->d3d12_surface.fence_value); + ID3D12Fence_SetEventOnCompletion(buffer->d3d12_surface.fence, buffer->d3d12_surface.fence_value, NULL); + + hr = S_OK; + +end: + if (list) ID3D12GraphicsCommandList_Release(list); + ID3D12Device_Release(device); + return hr; +} + +static HRESULT d3d12_surface_buffer_create_transfer_resource(struct buffer *buffer, ID3D12Resource **resource, MF2DBuffer_LockFlags flags) +{ + ID3D12Device *device; + D3D12_HEAP_PROPERTIES heap_prop = { .Type = flags == MF2DBuffer_LockFlags_Write ? D3D12_HEAP_TYPE_UPLOAD : D3D12_HEAP_TYPE_READBACK }; + D3D12_RESOURCE_DESC desc; + D3D12_SUBRESOURCE_FOOTPRINT footprint = buffer->d3d12_surface.layout.Footprint; + HRESULT hr; + + hr = ID3D12Resource_GetDevice(buffer->d3d12_surface.resource, &IID_ID3D12Device, (void **) &device); + if (FAILED(hr)) + return hr; + + memset(&desc, 0, sizeof(desc)); + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Alignment = 0; + desc.Width = footprint.RowPitch * footprint.Height; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = 0; + hr = ID3D12Device_CreateCommittedResource(device, &heap_prop, D3D12_HEAP_FLAG_NONE, &desc, + flags == MF2DBuffer_LockFlags_Write ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST, + NULL, &IID_ID3D12Resource, (void **) resource); + + ID3D12Device_Release(device); + return hr; +} + +static HRESULT d3d12_surface_buffer_map(struct buffer *buffer, MF2DBuffer_LockFlags flags) +{ + HRESULT hr = S_OK; + D3D12_RANGE empty_range = { 0, 0 }; + + if (!buffer->d3d12_surface.transfer) + { + ID3D12Resource **transfer = flags == MF2DBuffer_LockFlags_Write ? &buffer->d3d12_surface.upload : &buffer->d3d12_surface.readback; + if (!*transfer) + hr = d3d12_surface_buffer_create_transfer_resource(buffer, transfer, flags); + buffer->d3d12_surface.transfer = *transfer; + + if (SUCCEEDED(hr) && flags != MF2DBuffer_LockFlags_Write) + hr = d3d12_surface_buffer_copy_transfer_resource(buffer, flags); + } + + if (SUCCEEDED(hr)) + hr = ID3D12Resource_Map(buffer->d3d12_surface.transfer, 0, flags == MF2DBuffer_LockFlags_Write ? &empty_range : NULL, &buffer->d3d12_surface.data); + + if (FAILED(hr)) + buffer->d3d12_surface.transfer = NULL; + return hr; +} + +static HRESULT d3d12_surface_buffer_unmap(struct buffer *buffer, MF2DBuffer_LockFlags flags) +{ + HRESULT hr = S_OK; + D3D12_RANGE empty_range = { 0, 0 }; + ID3D12Resource_Unmap(buffer->d3d12_surface.transfer, 0, (flags == MF2DBuffer_LockFlags_Write && !buffer->d3d12_surface.keep_transfer) ? NULL : &empty_range); + + if (flags == MF2DBuffer_LockFlags_Read) + buffer->d3d12_surface.keep_transfer = FALSE; + else if (flags == MF2DBuffer_LockFlags_ReadWrite) + buffer->d3d12_surface.keep_transfer = TRUE; + + if (buffer->d3d12_surface.keep_transfer) + hr = E_INVALIDARG; + else + { + if (flags == MF2DBuffer_LockFlags_Write) + hr = d3d12_surface_buffer_copy_transfer_resource(buffer, flags); + + buffer->d3d12_surface.transfer = NULL; + } + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_Lock(IMFMediaBuffer *iface, BYTE **data, DWORD *max_length, + DWORD *current_length) +{ + struct buffer *buffer = impl_from_IMFMediaBuffer(iface); + HRESULT hr = S_OK; + + TRACE("%p, %p, %p, %p.\n", iface, data, max_length, current_length); + + if (!data) + return E_POINTER; + + EnterCriticalSection(&buffer->cs); + + if (!buffer->_2d.linear_buffer && buffer->_2d.locks) + hr = MF_E_INVALIDREQUEST; + else if (!buffer->_2d.linear_buffer) + { + if (!(buffer->_2d.linear_buffer = malloc(buffer->_2d.plane_size))) + hr = E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) + { + hr = d3d12_surface_buffer_map(buffer, MF2DBuffer_LockFlags_Read); + if (SUCCEEDED(hr)) + { + copy_image(buffer, buffer->_2d.linear_buffer, buffer->_2d.width, buffer->d3d12_surface.data, + buffer->d3d12_surface.layout.Footprint.RowPitch, buffer->_2d.width, buffer->_2d.height); + d3d12_surface_buffer_unmap(buffer, MF2DBuffer_LockFlags_Read); + } + } + } + + if (SUCCEEDED(hr)) + { + ++buffer->_2d.locks; + *data = buffer->_2d.linear_buffer; + if (max_length) + *max_length = buffer->_2d.plane_size; + if (current_length) + *current_length = buffer->_2d.plane_size; + } + + LeaveCriticalSection(&buffer->cs); + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_Unlock(IMFMediaBuffer *iface) +{ + struct buffer *buffer = impl_from_IMFMediaBuffer(iface); + HRESULT hr = S_OK; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&buffer->cs); + + if (!buffer->_2d.linear_buffer) + hr = HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED); + else if (!--buffer->_2d.locks) + { + free(buffer->_2d.linear_buffer); + buffer->_2d.linear_buffer = NULL; + } + + LeaveCriticalSection(&buffer->cs); + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_GetCurrentLength(IMFMediaBuffer *iface, DWORD *current_length) +{ + TRACE("%p.\n", iface); + + if (!current_length) + return E_INVALIDARG; + + *current_length = 0; + return S_OK; +} + +static HRESULT WINAPI d3d12_surface_buffer_SetCurrentLength(IMFMediaBuffer *iface, DWORD current_length) +{ + TRACE("%p, %lu.\n", iface, current_length); + return S_OK; +} + +static HRESULT d3d12_surface_buffer_lock(struct buffer *buffer, MF2DBuffer_LockFlags flags, + BYTE **scanline0, LONG *pitch, BYTE **buffer_start, DWORD *buffer_length) +{ + HRESULT hr = S_OK; + + if (buffer->_2d.linear_buffer) + hr = MF_E_UNEXPECTED; + else if (!buffer->_2d.locks) + hr = d3d12_surface_buffer_map(buffer, flags); + else if (buffer->_2d.lock_flags == MF2DBuffer_LockFlags_Write && flags != MF2DBuffer_LockFlags_Write) + hr = HRESULT_FROM_WIN32(ERROR_WAS_LOCKED); + + if (SUCCEEDED(hr)) + { + if (!buffer->_2d.locks) + buffer->_2d.lock_flags = flags; + buffer->_2d.locks++; + *scanline0 = buffer->d3d12_surface.data; + *pitch = buffer->d3d12_surface.layout.Footprint.RowPitch; + if (buffer_start) + *buffer_start = *scanline0; + if (buffer_length) + *buffer_length = *pitch * buffer->_2d.height; + } + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_Lock2D(IMF2DBuffer2 *iface, BYTE **scanline0, LONG *pitch) +{ + struct buffer *buffer = impl_from_IMF2DBuffer2(iface); + HRESULT hr; + + TRACE("%p, %p, %p.\n", iface, scanline0, pitch); + + if (!scanline0 || !pitch) + return E_POINTER; + + EnterCriticalSection(&buffer->cs); + + hr = d3d12_surface_buffer_lock(buffer, MF2DBuffer_LockFlags_ReadWrite, scanline0, pitch, NULL, NULL); + + LeaveCriticalSection(&buffer->cs); + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_Unlock2D(IMF2DBuffer2 *iface) +{ + struct buffer *buffer = impl_from_IMF2DBuffer2(iface); + HRESULT hr = S_OK; + + TRACE("%p.\n", iface); + + EnterCriticalSection(&buffer->cs); + + if (buffer->_2d.locks) + { + if (!--buffer->_2d.locks) + { + hr = d3d12_surface_buffer_unmap(buffer, buffer->_2d.lock_flags); + buffer->_2d.lock_flags = 0; + } + } + else + hr = HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED); + + LeaveCriticalSection(&buffer->cs); + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_GetScanline0AndPitch(IMF2DBuffer2 *iface, BYTE **scanline0, LONG *pitch) +{ + struct buffer *buffer = impl_from_IMF2DBuffer2(iface); + HRESULT hr = S_OK; + + TRACE("%p, %p, %p.\n", iface, scanline0, pitch); + + if (!scanline0 || !pitch) + return E_POINTER; + + EnterCriticalSection(&buffer->cs); + + if (!buffer->_2d.locks) + { + *scanline0 = NULL; + *pitch = 0; + hr = HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED); + } + else + { + *scanline0 = buffer->d3d12_surface.data; + *pitch = buffer->d3d12_surface.layout.Footprint.RowPitch; + } + + LeaveCriticalSection(&buffer->cs); + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_Lock2DSize(IMF2DBuffer2 *iface, MF2DBuffer_LockFlags flags, + BYTE **scanline0, LONG *pitch, BYTE **buffer_start, DWORD *buffer_length) +{ + struct buffer *buffer = impl_from_IMF2DBuffer2(iface); + HRESULT hr = S_OK; + + TRACE("%p, %#x, %p, %p, %p, %p.\n", iface, flags, scanline0, pitch, buffer_start, buffer_length); + + if (!scanline0 || !pitch || !buffer_start || !buffer_length) + return E_POINTER; + + EnterCriticalSection(&buffer->cs); + + hr = d3d12_surface_buffer_lock(buffer, flags, scanline0, pitch, buffer_start, buffer_length); + + LeaveCriticalSection(&buffer->cs); + + return hr; +} + +static HRESULT WINAPI d3d12_surface_buffer_GetResource(IMFDXGIBuffer *iface, REFIID riid, void **obj) +{ + struct buffer *buffer = impl_from_IMFDXGIBuffer(iface); + + TRACE("%p, %s, %p.\n", iface, debugstr_guid(riid), obj); + + return ID3D12Resource_QueryInterface(buffer->d3d12_surface.resource, riid, obj); +} + +static const IMFMediaBufferVtbl d3d12_surface_1d_buffer_vtbl = +{ + dxgi_1d_2d_buffer_QueryInterface, + memory_buffer_AddRef, + memory_buffer_Release, + d3d12_surface_buffer_Lock, + d3d12_surface_buffer_Unlock, + d3d12_surface_buffer_GetCurrentLength, + d3d12_surface_buffer_SetCurrentLength, + memory_buffer_GetMaxLength, +}; + +static const IMF2DBuffer2Vtbl d3d12_surface_buffer_vtbl = +{ + memory_2d_buffer_QueryInterface, + memory_2d_buffer_AddRef, + memory_2d_buffer_Release, + d3d12_surface_buffer_Lock2D, + d3d12_surface_buffer_Unlock2D, + d3d12_surface_buffer_GetScanline0AndPitch, + memory_2d_buffer_IsContiguousFormat, + memory_2d_buffer_GetContiguousLength, + memory_2d_buffer_ContiguousCopyTo, + memory_2d_buffer_ContiguousCopyFrom, + d3d12_surface_buffer_Lock2DSize, + memory_2d_buffer_Copy2DTo, +}; + +static const IMFDXGIBufferVtbl d3d12_surface_dxgi_buffer_vtbl = +{ + dxgi_buffer_QueryInterface, + dxgi_buffer_AddRef, + dxgi_buffer_Release, + d3d12_surface_buffer_GetResource, + dxgi_buffer_GetSubresourceIndex, + dxgi_buffer_GetUnknown, + dxgi_buffer_SetUnknown, +}; + static HRESULT memory_buffer_init(struct buffer *buffer, DWORD max_length, DWORD alignment, const IMFMediaBufferVtbl *vtbl) { @@ -1623,6 +2076,101 @@ static HRESULT create_dxgi_surface_buffer(IUnknown *surface, unsigned int sub_re return S_OK; } +static HRESULT create_d3d12_surface_buffer(IUnknown *surface, unsigned int sub_resource_idx, + BOOL bottom_up, IMFMediaBuffer **buffer) +{ + struct buffer *object = NULL; + ID3D12Device *device = NULL; + ID3D12Resource *resource = NULL; + D3D12_RESOURCE_DESC desc; + D3D12_PLACED_SUBRESOURCE_FOOTPRINT layout; + UINT64 total_bytes; + unsigned int stride; + D3DFORMAT format; + GUID subtype; + BOOL is_yuv; + HRESULT hr; + + if (FAILED(hr = IUnknown_QueryInterface(surface, &IID_ID3D12Resource, (void **)&resource))) + { + WARN("Failed to get resource interface, hr %#lx.\n", hr); + goto end; + } + + if (FAILED(hr = ID3D12Resource_GetDevice(resource, &IID_ID3D12Device, (void **) &device))) + goto end; + + desc = ID3D12Resource_GetDesc(resource); + ID3D12Device_GetCopyableFootprints(device, &desc, sub_resource_idx, 1, 0, &layout, NULL, NULL, &total_bytes); + layout.Offset = 0; + TRACE("format %#x, %u x %u.\n", layout.Footprint.Format, layout.Footprint.Width, layout.Footprint.Height); + + if (desc.Dimension != D3D12_RESOURCE_DIMENSION_TEXTURE2D) + { + hr = MF_E_INVALIDMEDIATYPE; + goto end; + } + + memcpy(&subtype, &MFVideoFormat_Base, sizeof(subtype)); + subtype.Data1 = format = MFMapDXGIFormatToDX9Format(layout.Footprint.Format); + + if (!(stride = mf_format_get_stride(&subtype, layout.Footprint.Width, &is_yuv))) + { + hr = MF_E_INVALIDMEDIATYPE; + goto end; + } + + if (!(object = calloc(1, sizeof(*object)))) + { + hr = E_OUTOFMEMORY; + goto end; + } + + object->IMFMediaBuffer_iface.lpVtbl = &d3d12_surface_1d_buffer_vtbl; + object->IMF2DBuffer2_iface.lpVtbl = &d3d12_surface_buffer_vtbl; + object->IMFDXGIBuffer_iface.lpVtbl = &d3d12_surface_dxgi_buffer_vtbl; + object->refcount = 1; + InitializeCriticalSection(&object->cs); + object->d3d12_surface.layout = layout; + object->d3d12_surface.resource = resource; + object->dxgi_surface.sub_resource_idx = sub_resource_idx; + + MFGetPlaneSize(format, + layout.Footprint.Width, layout.Footprint.Height, &object->_2d.plane_size); + object->_2d.width = stride; + object->_2d.height = layout.Footprint.Height; + object->_2d.copy_image = get_2d_buffer_copy_func(format); + + object->max_length = total_bytes; + + resource = NULL; + + if (FAILED(hr = init_attributes_object(&object->dxgi_surface.attributes, 0))) + goto end; + + if (FAILED(hr = MFCreateD3D12SynchronizationObject(device, + &IID_IMFD3D12SynchronizationObject, (void **) &object->d3d12_surface.sync_obj))) + goto end; + + if (FAILED(hr = IMFD3D12SynchronizationObject_QueryInterface(object->d3d12_surface.sync_obj, + &IID_IMFD3D12SynchronizationObjectCommands, (void **) &object->d3d12_surface.sync_cmd))) + goto end; + + IMFDXGIBuffer_SetUnknown(&object->IMFDXGIBuffer_iface, + &MF_D3D12_SYNCHRONIZATION_OBJECT, (IUnknown *) object->d3d12_surface.sync_obj); + IMFDXGIBuffer_SetUnknown(&object->IMFDXGIBuffer_iface, + &MF_D3D12_SYNCHRONIZATION_OBJECT, (IUnknown *) object->d3d12_surface.sync_cmd); + + *buffer = &object->IMFMediaBuffer_iface; + hr = S_OK; + +end: + if (device) ID3D12Device_Release(device); + if (resource) ID3D12Resource_Release(resource); + if (FAILED(hr) && object) IMFMediaBuffer_Release(&object->IMFMediaBuffer_iface); + return hr; +} + /*********************************************************************** * MFCreateMemoryBuffer (mfplat.@) */ @@ -1674,10 +2222,12 @@ HRESULT WINAPI MFCreateDXGISurfaceBuffer(REFIID riid, IUnknown *surface, UINT su { TRACE("%s, %p, %u, %d, %p.\n", debugstr_guid(riid), surface, subresource, bottom_up, buffer); - if (!IsEqualIID(riid, &IID_ID3D11Texture2D)) + if (IsEqualIID(riid, &IID_ID3D11Texture2D)) + return create_dxgi_surface_buffer(surface, subresource, bottom_up, buffer); + else if (IsEqualIID(riid, &IID_ID3D12Resource)) + return create_d3d12_surface_buffer(surface, subresource, bottom_up, buffer); + else return E_INVALIDARG; - - return create_dxgi_surface_buffer(surface, subresource, bottom_up, buffer); } static unsigned int buffer_get_aligned_length(unsigned int length, unsigned int alignment) diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index c81f8738d01..89ecf63d4f4 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -11777,7 +11777,6 @@ static void test_d3d12_surface_buffer(void) hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); if (hr == E_INVALIDARG) { - todo_wine win_skip("D3D12 resource buffers are not supported.\n"); ID3D12Resource_Release(resource); goto notsupported; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11420
Same as with !9777, this needs someone who knows d3d12. I have no idea what's going on. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_146079
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ ID3D12Device *device = NULL; + HRESULT hr; + + memset(rb, 0, sizeof(*rb)); + + rb->upload = upload; + rb->parent_resource = resource; + rb->sub_resource_idx = sub_resource_idx; + rb->sync = sync; + + hr = ID3D12Resource_GetDevice(rb->parent_resource, &IID_ID3D12Device, (void **) &device); + if (FAILED(hr)) goto end; + + desc = ID3D12Resource_GetDesc(rb->parent_resource); + ID3D12Device_GetCopyableFootprints(device, &desc, rb->sub_resource_idx, 1, 0, &rb->footprint, NULL, NULL, &bytes); + rb->footprint.Offset = 0; As far as I understand that's already guaranteed by `GetCopyableFootprints()`. Do you have evidence of the contrary?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147595
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ desc = ID3D12Resource_GetDesc(rb->parent_resource); + ID3D12Device_GetCopyableFootprints(device, &desc, rb->sub_resource_idx, 1, 0, &rb->footprint, NULL, NULL, &bytes); + rb->footprint.Offset = 0; + + memset(&desc, 0, sizeof(desc)); + desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; + desc.Alignment = 0; + desc.Width = bytes; + desc.Height = 1; + desc.DepthOrArraySize = 1; + desc.MipLevels = 1; + desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = 0; Fine, but personally I prefer `D3D12_RESOURCE_FLAG_NONE` explicitly.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147596
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ ID3D12Resource_Release(rb->resource); + rb->resource = NULL; + } + if (device) ID3D12Device_Release(device); + return hr; +} + +static void release_d3d12_resource_readback(struct d3d12_resource_readback *rb) +{ + D3D12_RANGE empty_range = { 0, 0 }; + + if (rb->resource) + { + ID3D12Resource_Unmap(rb->resource, 0, rb->upload ? NULL : &empty_range); + if (rb->upload) + copy_d3d12_resource_readback(rb); Maybe you can `ok()` the result.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147597
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ desc.Format = DXGI_FORMAT_UNKNOWN; + desc.SampleDesc.Count = 1; + desc.SampleDesc.Quality = 0; + desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; + desc.Flags = 0; + + hr = ID3D12Device_CreateCommittedResource(device, &heap_prop, D3D12_HEAP_FLAG_NONE, &desc, + rb->upload ? D3D12_RESOURCE_STATE_GENERIC_READ : D3D12_RESOURCE_STATE_COPY_DEST, NULL, + &IID_ID3D12Resource, (void **) &rb->resource); + if (FAILED(hr)) goto end; + + if (!rb->upload) + { + hr = copy_d3d12_resource_readback(rb); + if (FAILED(hr)) goto end; + } Shouldn't this happen also when you upload? Otherwise when you write a single pixel you're going to upload garbage data for the rest of the resource.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147598
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ + ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &pre_barrier); + ID3D12GraphicsCommandList_CopyTextureRegion(list, rb->upload ? &res_loc : &rb_loc, 0, 0, 0, rb->upload ? &rb_loc : &res_loc, NULL); + ID3D12GraphicsCommandList_ResourceBarrier(list, 1, &post_barrier); + ID3D12GraphicsCommandList_Close(list); + + if (!rb->upload) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReadyWait(rb->sync, queue); + + ID3D12CommandQueue_ExecuteCommandLists(queue, 1, (ID3D12CommandList **) &list); + + if (rb->upload) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReady(rb->sync, queue); + + ID3D12CommandQueue_Signal(queue, fence, 1); + ID3D12Fence_SetEventOnCompletion(fence, 1, NULL); Does it make sense to synchronize with the CPU only when downloading? Otherwise the previous `EnqueueResourceReady()` is useless anyway.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147599
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ ok(!*(DWORD *)data, "Unexpected buffer %#lx.\n", *(DWORD *)data); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2DSize(Write) -> Unlock2D() success */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Lock2DSize(ReadWrite) -> Unlock2D() failure */ + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_ReadWrite, &data, &pitch, &data2, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); Do you understand why that's happening? It's unexpected to me, according to what I remember of this API.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147600
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/buffer.c:
+ { + hr = ID3D12Device_CreateCommandAllocator(device, D3D12_COMMAND_LIST_TYPE_COPY, + &IID_ID3D12CommandAllocator, (void **) &buffer->d3d12_surface.allocator); + if (FAILED(hr)) + goto end; + } + + if (!buffer->d3d12_surface.fence) + { + hr = ID3D12Device_CreateFence(device, 0, 0, &IID_ID3D12Fence, (void **) &buffer->d3d12_surface.fence); + if (FAILED(hr)) + goto end; + } + + hr = ID3D12Device_CreateCommandList(device, 0, D3D12_COMMAND_LIST_TYPE_COPY, buffer->d3d12_surface.allocator, NULL, + &IID_ID3D12GraphicsCommandList, (void **) &list); The command allocator should be periodically be reset, otherwise it will grow without limits. The annoyance is that it mustn't be reset while any command list allocated out of it is still in execution. The strategy I'd go for is to have two command allocators: at each point in time one of the two is active (you allocate command lists out of it) and the other is just waiting for the command list it has to finish execution. Once execution for all the command lists in the stand-by command allocator has finished (you can see that with the fence value), you reset it and then swap roles.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147601
First round of review. Please try to make smaller commits, these are quite large and easy to split. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147602
On Sun Aug 2 15:26:11 2026 +0000, Giovanni Mascellani wrote:
Does it make sense to synchronize with the CPU only when downloading? Otherwise the previous `EnqueueResourceReady()` is useless anyway. Disregard this. You actually have to synchronize to the CPU to release the command allocator and the queue.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_147604
On Sun Aug 2 15:26:11 2026 +0000, Giovanni Mascellani wrote:
Do you understand why that's happening? It's unexpected to me, according to what I remember of this API. I believe it is a mistake in the windows implementation of the d3d12-backed buffer. Running the test with d3d12 validation layers enabled points to d3d12 API misuse specifically with read-write locking, while just read locking or just write locking works as expected. I've found it easier to explicitly test for the error code instead of working around it.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148010
On Wed Aug 5 12:55:33 2026 +0000, Giovanni Mascellani wrote:
First round of review. Please try to make smaller commits, these are quite large and easy to split. I'm not sure where to split them - both commits seem mostly atomic to me considering they're each just adding a chunk of mostly continuous and logically related code.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148019
On Sun Aug 2 15:26:09 2026 +0000, Giovanni Mascellani wrote:
As far as I understand that's already guaranteed by `GetCopyableFootprints()`. Do you have evidence of the contrary? I'm not sure I fully understand the purpose of this field, from the wording I assumed it might be nonzero for a nonzero subresource index, but that doesn't seem to be the case. The line can be removed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148047
On Wed Aug 5 12:55:33 2026 +0000, Charlotte Pabst wrote:
I'm not sure where to split them - both commits seem mostly atomic to me considering they're each just adding a chunk of mostly continuous and logically related code. Tests are rather simple to split; indeed, they are already split by comment lines (thanks for those!), but then they are lumped together in a single huge commit. If you introduce them incrementally in a handful of different commits it's easier for me to review each commit alone. Take it with a grain of salt: it doesn't have to be five lines per commit; but neither it has to be 800. I guess aiming for around 100-200 lines per commit introducing tests is a good compromise for me.
For business code things can become a bit more complicated in general. But here you're implementing something that wasn't available before, so you don't even have to worry too much about regressions: you begin with a commit that introduces the COM boilerplate and nothing else (everything returns `E_NOTIMPL`), then implement related groups of methods together in a commit. For example, first `Lock()` and `Unlock()`, then `Lock2D()` and `Unlock2D()`, then `Lock2DSize()`. My rough estimate is that changes like those you're proposing here should fit 2 MRs: one MR with five commits introducing tests and another one with other five commits introducing the business code. In general, I expect the time needed to get a MR accepted is at least quadratic in its size: reviewing a MR version takes at least linear time in its size (could be more, if its size makes it more complicated to understand); and the number of times that will have to happen is proportional to the number of comments the reviewer will have, which is also likely linear in the size of the MR. And that's discounting the fact that a reviewer with put off reviewing a huge MR until they have a good amount of time and concentration. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148319
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ + hr = IMF2DBuffer_Unlock2D(_2d_buffer); + ok(hr == HRESULT_FROM_WIN32(ERROR_WAS_UNLOCKED), "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Lock(buffer, &data, NULL, NULL); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMF2DBuffer_Lock2D(_2d_buffer, &data, &pitch); + ok(hr == MF_E_UNEXPECTED, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_Unlock(buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IMF2DBuffer_Release(_2d_buffer); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2dbuffer2); I think you never released it, so you don't need to query again here.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148320
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ status = WaitForSingleObject(event, 100); + ok(status == WAIT_TIMEOUT, "got %#lx.\n", status); + hr = IMF2DBuffer2_Lock2DSize(_2dbuffer2, MF2DBuffer_LockFlags_Write, &data, &pitch, &buffer_start, &length); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_TIMEOUT, "got %#lx.\n", status); + hr = IMF2DBuffer2_Unlock2D(_2dbuffer2); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + status = WaitForSingleObject(event, 100); + ok(status == WAIT_OBJECT_0, "got %#lx.\n", status); + IMFD3D12SynchronizationObjectCommands_Release(sync_cmd); + IMFDXGIBuffer_Release(dxgi_buffer); + IMF2DBuffer2_Release(_2dbuffer2); + IMFMediaBuffer_Release(buffer); + + /* Bottom up. */ I'm not sure of what "bottom up" means here and what this section is supposed to test.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148321
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + + desc.Flags = 0; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_COMMON, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 0, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + + /* Subresource index 1. */ Might be a good place to test again `GetSubresourceIndex()`.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148322
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/tests/mfplat.c:
+ IMFMediaBuffer_Release(buffer); + ID3D12Resource_Release(resource); + + /* Subresource index 1. */ + desc.MipLevels = 0; + desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; + hr = ID3D12Device_CreateCommittedResource(device, &heap_props, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_RENDER_TARGET, NULL, &IID_ID3D12Resource, (void **)&resource); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = pMFCreateDXGISurfaceBuffer(&IID_ID3D12Resource, (IUnknown *)resource, 1, FALSE, &buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IMFMediaBuffer_QueryInterface(buffer, &IID_IMF2DBuffer2, (void **)&_2d_buffer); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + /* Pitch reflects top level. */ Are you testing anything related to pitch here?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148323
On Sun Aug 2 15:26:12 2026 +0000, Giovanni Mascellani wrote:
The command allocator should be periodically be reset, otherwise it will grow without limits. The annoyance is that it mustn't be reset while any command list allocated out of it is still in execution. The strategy I'd go for is to have two command allocators: at each point in time one of the two is active (you allocate command lists out of it) and the other is just waiting for the command list it has to finish execution. Once execution for all the command lists in the stand-by command allocator has finished (you can see that with the fence value), you reset it and then swap roles. OTOH, there is no need to destroy and recreate the command list each time. You can keep one in the object and call `Reset()` each time. It's even easier than the allocator, because you can `Reset()` right away after having submitted the command list. The internal GPU command buffer is owned by the command allocator, not by the command list. Not that I expect creating and destroying a command list to be particularly expensive anyway.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148324
Giovanni Mascellani (@giomasce) commented about dlls/mfplat/buffer.c:
+ if (FAILED(hr)) + goto end; + + if (flags != MF2DBuffer_LockFlags_Write) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReadyWait(buffer->d3d12_surface.sync_cmd, buffer->d3d12_surface.queue); + + ID3D12CommandQueue_ExecuteCommandLists(buffer->d3d12_surface.queue, 1, (ID3D12CommandList **) &list); + + if (flags == MF2DBuffer_LockFlags_Write) + IMFD3D12SynchronizationObjectCommands_EnqueueResourceReady(buffer->d3d12_surface.sync_cmd, buffer->d3d12_surface.queue); + else + IMFD3D12SynchronizationObjectCommands_EnqueueResourceRelease(buffer->d3d12_surface.sync_cmd, buffer->d3d12_surface.queue); + + buffer->d3d12_surface.fence_value++; + ID3D12CommandQueue_Signal(buffer->d3d12_surface.queue, buffer->d3d12_surface.fence, buffer->d3d12_surface.fence_value); + ID3D12Fence_SetEventOnCompletion(buffer->d3d12_surface.fence, buffer->d3d12_surface.fence_value, NULL); Do you have to wait when writing back? It seems you could skip it, synchronizing with the consume will be guaranteed by the MF synchronization object.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148325
On Wed Aug 5 16:16:32 2026 +0000, Charlotte Pabst wrote:
I'm not sure I fully understand the purpose of this field, from the wording I assumed it might be nonzero for a nonzero subresource index, but that doesn't seem to be the case. The line can be removed. The offset makes sense when you're calling `GetCopyableFootprints()` on more than one subresource. Then the resources that are not the first one will get a positive offset (or, in general, an offset larger than the base offset you specified), so that they do not overlap on the buffer.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148326
But here you're implementing something that wasn't available before, so you don't even have to worry too much about regressions
I'm not sure about that. Currently all of the tests are guarded by a skip if d3d12 buffers are not implemented. If all tests are added first and the implementation step-by-step afterwards, unimplemented functions can interfere with implemented functions - sometimes in non-trivial ways, for example in the lock incompatibilities test, a lock operation that was expected to fail (due to a lock incompatibility) might now succeed because a previous lock operation wasn't implemented yet. All locking functions also essentially use the same machinery (d3d12_surface_buffer_map/d3d12_surface_buffer_unmap), so all of the actual locking logic would be added along with the first lock function implemented anyway. I don't have any objections to splitting the tests however. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148487
On Fri Aug 7 15:29:38 2026 +0000, Giovanni Mascellani wrote:
Are you testing anything related to pitch here? Doesn't seem so. This section is based on an equivalent section in the d3d11 buffer tests, which seems to test the fact that the buffer pitch for a subresource is always the same as for subresource 0, but that does not seem to be the case for d3d12-backed buffers (the pitch seems to be exactly as one would expect normally, which here is `max(width*4, 256)`). I've double checked this for larger values for width too. Hence I think it doesn't make sense to test it and the comment can just be removed.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148764
On Fri Aug 7 15:29:38 2026 +0000, Giovanni Mascellani wrote:
I'm not sure of what "bottom up" means here and what this section is supposed to test. MFCreateDXGISurfaceBuffer (and other buffer creation functions) has a parameter called bottom_up. It doesn't seem to actually be doing anything for d3d-backed buffers (it seems to only really do anything for MFCreate2DMediaBuffer). However this section tests that creation and usage works normally with bottom_up, it's based on the d3d11 buffer tests that contain an equivalent section.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148766
On Sun Aug 2 15:26:11 2026 +0000, Giovanni Mascellani wrote:
Shouldn't this happen also when you upload? Otherwise when you write a single pixel you're going to upload garbage data for the rest of the resource. Alternatively, do you think using CopyTextureRegion instead of always copying the entire resource could be a good idea?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148768
On Wed Aug 12 19:14:35 2026 +0000, Charlotte Pabst wrote:
Alternatively, do you think using CopyTextureRegion instead of always copying the entire resource could be a good idea? Or rather, making use of its DstX, DstY and SrcBox parameters.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148774
If all tests are added first and the implementation step-by-step afterwards, unimplemented functions can interfere with implemented functions - sometimes in non-trivial ways, for example in the lock incompatibilities test, a lock operation that was expected to fail (due to a lock incompatibility) might now succeed because a previous lock operation wasn't implemented yet.
You can use `wine_todo` for that. Also, it certainly makes sense to skip the rest of a test if at any point you receive `E_NOTIMPL` and there is no reasonable way to recover a testable state from that.
All locking functions also essentially use the same machinery (d3d12_surface_buffer_map/d3d12_surface_buffer_unmap), so all of the actual locking logic would be added along with the first lock function implemented anyway.
Sure, but still the individual commits become smaller and I don't have to read everything again each time I come back to this MR. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148820
On Wed Aug 12 19:34:10 2026 +0000, Charlotte Pabst wrote:
Or rather, making use of its DstX, DstY and SrcBox parameters. Sure, I have no problem with that. It's more complicated, and being this a test I'd go for less complexity rather than more performance, but as you wish.
In principle you could even leave as it is, since I think every time you set a pixel you don't really care about what all the other pixels do. It might become confusing later if somebody (reasonably) thinks that setting a pixel doesn't change all the others, and wonder why the test doesn't work. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148821
On Thu Aug 13 13:09:12 2026 +0000, Charlotte Pabst wrote:
MFCreateDXGISurfaceBuffer (and other buffer creation functions) has a parameter called bottom_up. It doesn't seem to actually be doing anything for d3d-backed buffers (it seems to only really do anything for MFCreate2DMediaBuffer). However this section tests that creation and usage works normally with bottom_up, it's based on the d3d11 buffer tests that contain an equivalent section. Ah, right, I had missed that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11420#note_148822
participants (4)
-
Charlotte Pabst -
Charlotte Pabst (@CharlottePabst) -
Giovanni Mascellani (@giomasce) -
Nikolay Sivov (@nsivov)