diff options
| author | Stenzek <stenzek@gmail.com> | 2016-03-06 18:48:35 +1000 |
|---|---|---|
| committer | Stenzek <stenzek@gmail.com> | 2016-05-08 23:18:51 +1000 |
| commit | 6f3573dda87d2796a031fb7afc5ac1cecbb7ee2f (patch) | |
| tree | 856fd55a31f9216664579f25491225f2c743c6ff /Source/Core/VideoBackends/D3D12/XFBEncoder.cpp | |
| parent | 3372bfa6ab72c38763ed5b37dcb0fe6a9068a7eb (diff) | |
D3D12: Implement XFB encoding/decoding (support Real XFB)
Diffstat (limited to 'Source/Core/VideoBackends/D3D12/XFBEncoder.cpp')
| -rw-r--r-- | Source/Core/VideoBackends/D3D12/XFBEncoder.cpp | 165 |
1 files changed, 155 insertions, 10 deletions
diff --git a/Source/Core/VideoBackends/D3D12/XFBEncoder.cpp b/Source/Core/VideoBackends/D3D12/XFBEncoder.cpp index a2933ed268..f7147c0c04 100644 --- a/Source/Core/VideoBackends/D3D12/XFBEncoder.cpp +++ b/Source/Core/VideoBackends/D3D12/XFBEncoder.cpp @@ -1,4 +1,4 @@ -// Copyright 2011 Dolphin Emulator Project +// Copyright 2016 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. @@ -6,33 +6,178 @@ #include "Common/MsgHandler.h" #include "Common/Logging/Log.h" #include "VideoBackends/D3D12/D3DBase.h" +#include "VideoBackends/D3D12/D3DCommandListManager.h" #include "VideoBackends/D3D12/D3DShader.h" #include "VideoBackends/D3D12/D3DState.h" +#include "VideoBackends/D3D12/D3DUtil.h" #include "VideoBackends/D3D12/FramebufferManager.h" #include "VideoBackends/D3D12/Render.h" +#include "VideoBackends/D3D12/StaticShaderCache.h" #include "VideoBackends/D3D12/XFBEncoder.h" -// D3D12TODO: Convert this file.. - namespace DX12 { +// YUYV data is packed into half-width RGBA, with Y values in (R,B) and UV in (G,A) +constexpr size_t XFB_TEXTURE_WIDTH = MAX_XFB_WIDTH / 2; +constexpr size_t XFB_TEXTURE_HEIGHT = MAX_XFB_HEIGHT; + +// Buffer enough space for 2 XFB buffers (our frame latency) +constexpr size_t XFB_UPLOAD_BUFFER_SIZE = D3D::AlignValue(XFB_TEXTURE_WIDTH * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT) * XFB_TEXTURE_HEIGHT * 2; +constexpr size_t XFB_ENCODER_PARAMS_BUFFER_SIZE = 64 * 1024; + +std::unique_ptr<XFBEncoder> g_xfb_encoder; + XFBEncoder::XFBEncoder() -{ } +{ + ID3D12Resource* texture; + + CheckHR(D3D::device12->CreateCommittedResource( + &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), + D3D12_HEAP_FLAG_NONE, + &CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_R8G8B8A8_UNORM, XFB_TEXTURE_WIDTH, XFB_TEXTURE_HEIGHT, 1, 1, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET), + D3D12_RESOURCE_STATE_RENDER_TARGET, + nullptr, + IID_PPV_ARGS(&texture))); -void XFBEncoder::Init() + m_yuyv_texture = new D3DTexture2D(texture, + (D3D11_BIND_FLAG)(D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET), + DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_R8G8B8A8_UNORM); + SAFE_RELEASE(texture); + + CheckHR(D3D::device12->CreateCommittedResource( + &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_READBACK), + D3D12_HEAP_FLAG_NONE, + &CD3DX12_RESOURCE_DESC::Buffer(D3D::AlignValue(XFB_TEXTURE_WIDTH * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT) * MAX_XFB_HEIGHT), + D3D12_RESOURCE_STATE_COPY_DEST, + nullptr, + IID_PPV_ARGS(&m_readback_buffer))); + + m_upload_buffer = std::make_unique<D3DStreamBuffer>(XFB_UPLOAD_BUFFER_SIZE, XFB_UPLOAD_BUFFER_SIZE, nullptr); + m_encode_params_buffer = std::make_unique<D3DStreamBuffer>(XFB_ENCODER_PARAMS_BUFFER_SIZE, XFB_ENCODER_PARAMS_BUFFER_SIZE, nullptr); +} + +XFBEncoder::~XFBEncoder() { - // D3D12TODO: Convert this file.. + SAFE_RELEASE(m_yuyv_texture); + SAFE_RELEASE(m_readback_buffer); } -void XFBEncoder::Shutdown() +void XFBEncoder::EncodeTextureToRam(u8* dst, u32 dst_pitch, u32 dst_height, + D3DTexture2D* src_texture, const TargetRectangle& src_rect, + u32 src_width, u32 src_height, float gamma) { - // D3D12TODO: Convert this file.. + // src_rect is in native coordinates + // dst_pitch is in words + u32 dst_width = dst_pitch / 2; + u32 dst_texture_width = dst_width / 2; + _assert_msg_(VIDEO, dst_width <= MAX_XFB_WIDTH && dst_height <= MAX_XFB_HEIGHT, "XFB destination does not exceed maximum size"); + + // Encode parameters constant buffer used by shader + struct EncodeParameters + { + float srcRect[4]; + float texelSize[2]; + float pad[2]; + }; + EncodeParameters parameters = + { + { + static_cast<float>(src_rect.left) / static_cast<float>(src_width), + static_cast<float>(src_rect.top) / static_cast<float>(src_height), + static_cast<float>(src_rect.right) / static_cast<float>(src_width), + static_cast<float>(src_rect.bottom) / static_cast<float>(src_height) + }, + { + 1.0f / EFB_WIDTH, + 1.0f / EFB_HEIGHT + }, + { + 0.0f, + 0.0f + } + }; + m_encode_params_buffer->AllocateSpaceInBuffer(sizeof(parameters), D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT); + memcpy(m_encode_params_buffer->GetCPUAddressOfCurrentAllocation(), ¶meters, sizeof(parameters)); + + // Convert RGBA texture to YUYV intermediate texture. + // Performs downscaling through a linear filter. Probably not ideal, but it's not going to look perfect anyway. + CD3DX12_RECT src_texture_rect(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom); + D3D12_RESOURCE_STATES src_texture_state = src_texture->GetResourceUsageState(); + m_yuyv_texture->TransitionToResourceState(D3D::current_command_list, D3D12_RESOURCE_STATE_RENDER_TARGET); + D3D::current_command_list->OMSetRenderTargets(1, &m_yuyv_texture->GetRTV12(), FALSE, nullptr); + D3D::current_command_list->SetGraphicsRootConstantBufferView(DESCRIPTOR_TABLE_PS_CBVONE, m_encode_params_buffer->GetGPUAddressOfCurrentAllocation()); + D3D::command_list_mgr->SetCommandListDirtyState(COMMAND_LIST_STATE_PS_CBV, true); + D3D::SetViewportAndScissor(0, 0, dst_texture_width, dst_height); + D3D::SetLinearCopySampler(); + D3D::DrawShadedTexQuad( + src_texture, &src_texture_rect, src_rect.GetWidth(), src_rect.GetHeight(), + StaticShaderCache::GetXFBEncodePixelShader(), StaticShaderCache::GetSimpleVertexShader(), StaticShaderCache::GetSimpleVertexShaderInputLayout(), + {}, gamma, 0, DXGI_FORMAT_R8G8B8A8_UNORM, false, false); + + src_texture->TransitionToResourceState(D3D::current_command_list, src_texture_state); + + // Copy from YUYV intermediate texture to readback buffer. It's likely the pitch here is going to be different to dst_pitch. + u32 readback_pitch = D3D::AlignValue(dst_width * sizeof(u16), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + D3D12_PLACED_SUBRESOURCE_FOOTPRINT dst_footprint = { 0, { DXGI_FORMAT_R8G8B8A8_UNORM, dst_texture_width, dst_height, 1, readback_pitch } }; + CD3DX12_TEXTURE_COPY_LOCATION dst_location(m_readback_buffer, dst_footprint); + CD3DX12_TEXTURE_COPY_LOCATION src_location(m_yuyv_texture->GetTex12(), 0); + CD3DX12_BOX src_box(0, 0, dst_texture_width, dst_height); + m_yuyv_texture->TransitionToResourceState(D3D::current_command_list, D3D12_RESOURCE_STATE_COPY_SOURCE); + D3D::current_command_list->CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, &src_box); + + // Wait until the GPU completes the copy. Resets back to known state automatically. + D3D::command_list_mgr->ExecuteQueuedWork(true); + + // Copy from the readback buffer to dst. + // Can't be done as one memcpy due to pitch difference. + void* readback_texture_map; + CheckHR(m_readback_buffer->Map(0, nullptr, &readback_texture_map)); + + for (u32 row = 0; row < dst_height; row++) + { + const u8* row_src = reinterpret_cast<u8*>(readback_texture_map) + readback_pitch * row; + u8* row_dst = dst + dst_pitch * row; + memcpy(row_dst, row_src, std::min(dst_pitch, readback_pitch)); + } + + m_readback_buffer->Unmap(0, nullptr); } -void XFBEncoder::Encode(u8* dst, u32 width, u32 height, const EFBRectangle& srcRect, float gamma) +void XFBEncoder::DecodeToTexture(D3DTexture2D* dst_texture, const u8* src, u32 src_width, u32 src_height) { - // D3D12TODO: Convert this file.. + _assert_msg_(VIDEO, src_width <= MAX_XFB_WIDTH && src_height <= MAX_XFB_HEIGHT, "XFB source does not exceed maximum size"); + + // Copy to XFB upload buffer. Each row has to be done separately due to pitch differences. + u32 buffer_pitch = D3D::AlignValue(src_width / 2 * sizeof(u32), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT); + m_upload_buffer->AllocateSpaceInBuffer(buffer_pitch * src_height, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT); + for (u32 row = 0; row < src_height; row++) + { + const u8* row_src = src + (src_width * 2) * row; + u8* row_dst = reinterpret_cast<u8*>(m_upload_buffer->GetCPUAddressOfCurrentAllocation()) + buffer_pitch * row; + memcpy(row_dst, row_src, src_width * 2); + } + + // Copy from upload buffer to intermediate YUYV texture. + D3D12_PLACED_SUBRESOURCE_FOOTPRINT src_footprint = { m_upload_buffer->GetOffsetOfCurrentAllocation(), { DXGI_FORMAT_R8G8B8A8_UNORM, src_width / 2, src_height, 1, buffer_pitch } }; + CD3DX12_TEXTURE_COPY_LOCATION src_location(m_upload_buffer->GetBuffer(), src_footprint); + CD3DX12_TEXTURE_COPY_LOCATION dst_location(m_yuyv_texture->GetTex12(), 0); + CD3DX12_BOX src_box(0, 0, src_width / 2, src_height); + m_yuyv_texture->TransitionToResourceState(D3D::current_command_list, D3D12_RESOURCE_STATE_COPY_DEST); + D3D::current_command_list->CopyTextureRegion(&dst_location, 0, 0, 0, &src_location, &src_box); + + // Convert YUYV texture to RGBA texture with pixel shader. + CD3DX12_RECT src_texture_rect(0, 0, src_width / 2, src_height); + dst_texture->TransitionToResourceState(D3D::current_command_list, D3D12_RESOURCE_STATE_RENDER_TARGET); + D3D::current_command_list->OMSetRenderTargets(1, &dst_texture->GetRTV12(), FALSE, nullptr); + D3D::SetViewportAndScissor(0, 0, src_width, src_height); + D3D::DrawShadedTexQuad( + m_yuyv_texture, &src_texture_rect, XFB_TEXTURE_WIDTH, XFB_TEXTURE_HEIGHT, + StaticShaderCache::GetXFBDecodePixelShader(), StaticShaderCache::GetSimpleVertexShader(), StaticShaderCache::GetSimpleVertexShaderInputLayout(), + {}, 1.0f, 0, DXGI_FORMAT_R8G8B8A8_UNORM, false, false); + + // XFB source textures are expected to be in shader resource state. + dst_texture->TransitionToResourceState(D3D::current_command_list, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); } } |
