// Copyright 2011 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "VideoBackends/D3D/PSTextureEncoder.h" #include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Core/HW/Memmap.h" #include "VideoBackends/D3D/D3DBase.h" #include "VideoBackends/D3D/D3DShader.h" #include "VideoBackends/D3D/D3DState.h" #include "VideoBackends/D3D/D3DUtil.h" #include "VideoBackends/D3D/DXTexture.h" #include "VideoBackends/D3D/FramebufferManager.h" #include "VideoBackends/D3D/Render.h" #include "VideoBackends/D3D/TextureCache.h" #include "VideoBackends/D3D/VertexShaderCache.h" #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/AbstractTexture.h" #include "VideoCommon/TextureConversionShader.h" #include "VideoCommon/VideoCommon.h" namespace DX11 { struct EFBEncodeParams { s32 SrcLeft; s32 SrcTop; u32 DestWidth; u32 ScaleFactor; float y_scale; float gamma_rcp; float clamp_top; float clamp_bottom; s32 filter_coefficients[3]; u32 padding; }; PSTextureEncoder::PSTextureEncoder() { } PSTextureEncoder::~PSTextureEncoder() = default; void PSTextureEncoder::Init() { // TODO: Move this to a constant somewhere in common. TextureConfig encoding_texture_config(EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, true); m_encoding_render_texture = g_renderer->CreateTexture(encoding_texture_config); m_encoding_readback_texture = g_renderer->CreateStagingTexture(StagingTextureType::Readback, encoding_texture_config); ASSERT(m_encoding_render_texture && m_encoding_readback_texture); // Create constant buffer for uploading data to shaders D3D11_BUFFER_DESC bd = CD3D11_BUFFER_DESC(sizeof(EFBEncodeParams), D3D11_BIND_CONSTANT_BUFFER); HRESULT hr = D3D::device->CreateBuffer(&bd, nullptr, &m_encode_params); CHECK(SUCCEEDED(hr), "create efb encode params buffer"); D3D::SetDebugObjectName(m_encode_params, "efb encoder params buffer"); } void PSTextureEncoder::Shutdown() { for (auto& it : m_encoding_shaders) SAFE_RELEASE(it.second); m_encoding_shaders.clear(); SAFE_RELEASE(m_encode_params); } void PSTextureEncoder::Encode( u8* dst, const EFBCopyParams& params, u32 native_width, u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, const EFBRectangle& src_rect, bool scale_by_half, float y_scale, float gamma, bool clamp_top, bool clamp_bottom, const TextureCacheBase::CopyFilterCoefficientArray& filter_coefficients) { // Resolve MSAA targets before copying. // FIXME: Instead of resolving EFB, it would be better to pick out a // single sample from each pixel. The game may break if it isn't // expecting the blurred edges around multisampled shapes. ID3D11ShaderResourceView* pEFB = params.depth ? FramebufferManager::GetResolvedEFBDepthTexture()->GetSRV() : FramebufferManager::GetResolvedEFBColorTexture()->GetSRV(); // Reset API g_renderer->ResetAPIState(); // Set up all the state for EFB encoding { const u32 words_per_row = bytes_per_row / sizeof(u32); D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, FLOAT(words_per_row), FLOAT(num_blocks_y)); D3D::context->RSSetViewports(1, &vp); constexpr EFBRectangle fullSrcRect(0, 0, EFB_WIDTH, EFB_HEIGHT); TargetRectangle targetRect = g_renderer->ConvertEFBRectangle(fullSrcRect); D3D::context->OMSetRenderTargets( 1, &static_cast(m_encoding_render_texture.get())->GetRawTexIdentifier()->GetRTV(), nullptr); EFBEncodeParams encode_params; encode_params.SrcLeft = src_rect.left; encode_params.SrcTop = src_rect.top; encode_params.DestWidth = native_width; encode_params.ScaleFactor = scale_by_half ? 2 : 1; encode_params.y_scale = y_scale; encode_params.gamma_rcp = 1.0f / gamma; encode_params.clamp_top = clamp_top ? src_rect.top / float(EFB_HEIGHT) : 0.0f; encode_params.clamp_bottom = clamp_bottom ? src_rect.bottom / float(EFB_HEIGHT) : 0.0f; for (size_t i = 0; i < filter_coefficients.size(); i++) encode_params.filter_coefficients[i] = filter_coefficients[i]; D3D::context->UpdateSubresource(m_encode_params, 0, nullptr, &encode_params, 0, 0); D3D::stateman->SetPixelConstants(m_encode_params); // We also linear filtering for both box filtering and downsampling higher resolutions to 1x // TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more // complex down filtering to average all pixels and produce the correct result. // Also, box filtering won't be correct for anything other than 1x IR if (scale_by_half || g_renderer->GetEFBScale() != 1 || y_scale > 1.0f) D3D::SetLinearCopySampler(); else D3D::SetPointCopySampler(); D3D::drawShadedTexQuad(pEFB, targetRect.AsRECT(), g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), GetEncodingPixelShader(params), VertexShaderCache::GetSimpleVertexShader(), VertexShaderCache::GetSimpleInputLayout()); // Copy to staging buffer MathUtil::Rectangle copy_rect(0, 0, words_per_row, num_blocks_y); m_encoding_readback_texture->CopyFromTexture(m_encoding_render_texture.get(), copy_rect, 0, 0, copy_rect); m_encoding_readback_texture->Flush(); if (m_encoding_readback_texture->Map()) { m_encoding_readback_texture->ReadTexels(copy_rect, dst, memory_stride); m_encoding_readback_texture->Unmap(); } } g_renderer->RestoreAPIState(); } ID3D11PixelShader* PSTextureEncoder::GetEncodingPixelShader(const EFBCopyParams& params) { auto iter = m_encoding_shaders.find(params); if (iter != m_encoding_shaders.end()) return iter->second; D3DBlob* bytecode = nullptr; const char* shader = TextureConversionShaderTiled::GenerateEncodingShader(params, APIType::D3D); if (!D3D::CompilePixelShader(shader, &bytecode)) { PanicAlert("Failed to compile texture encoding shader."); m_encoding_shaders[params] = nullptr; return nullptr; } ID3D11PixelShader* newShader; HRESULT hr = D3D::device->CreatePixelShader(bytecode->Data(), bytecode->Size(), nullptr, &newShader); CHECK(SUCCEEDED(hr), "create efb encoder pixel shader"); m_encoding_shaders.emplace(params, newShader); return newShader; } }