// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include #include #include "Common/CommonWindows.h" #include #include #include "GPU/ge_constants.h" #include "GPU/GPUState.h" #include "GPU/GPUDefinitions.h" #include "GPU/Common/GPUStateUtils.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/D3D11/TextureCacheD3D11.h" #include "GPU/D3D11/FramebufferManagerD3D11.h" #include "GPU/D3D11/D3D11Util.h" #include "Core/Config.h" #include "ext/xxhash.h" using namespace Microsoft::WRL; // For depth depal struct DepthPushConstants { float z_scale; float z_offset; float pad[2]; }; static const D3D11_INPUT_ELEMENT_DESC g_QuadVertexElements[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, }, { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12,}, }; // NOTE: In the D3D backends, we flip R and B in the shaders, so while these look wrong, they're OK. static Draw::DataFormat FromD3D11Format(u32 fmt) { switch (fmt) { case DXGI_FORMAT_B4G4R4A4_UNORM: return Draw::DataFormat::A4R4G4B4_UNORM_PACK16; case DXGI_FORMAT_B5G5R5A1_UNORM: return Draw::DataFormat::A1R5G5B5_UNORM_PACK16; case DXGI_FORMAT_B5G6R5_UNORM: return Draw::DataFormat::R5G6B5_UNORM_PACK16; case DXGI_FORMAT_R8_UNORM: return Draw::DataFormat::R8_UNORM; case DXGI_FORMAT_R8G8B8A8_UNORM: default: return Draw::DataFormat::R8G8B8A8_UNORM; } } static DXGI_FORMAT ToDXGIFormat(Draw::DataFormat fmt) { switch (fmt) { case Draw::DataFormat::BC1_RGBA_UNORM_BLOCK: return DXGI_FORMAT_BC1_UNORM; case Draw::DataFormat::BC2_UNORM_BLOCK: return DXGI_FORMAT_BC2_UNORM; case Draw::DataFormat::BC3_UNORM_BLOCK: return DXGI_FORMAT_BC3_UNORM; case Draw::DataFormat::BC4_UNORM_BLOCK: return DXGI_FORMAT_BC4_UNORM; case Draw::DataFormat::BC5_UNORM_BLOCK: return DXGI_FORMAT_BC5_UNORM; case Draw::DataFormat::BC7_UNORM_BLOCK: return DXGI_FORMAT_BC7_UNORM; case Draw::DataFormat::R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM; default: _dbg_assert_(false); return DXGI_FORMAT_UNKNOWN; } } SamplerCacheD3D11::~SamplerCacheD3D11() { } HRESULT SamplerCacheD3D11::GetOrCreateSampler(ID3D11Device *device, const SamplerCacheKey &key, ID3D11SamplerState **ppSamplerState) { auto iter = cache_.find(key); if (iter != cache_.end()) { iter->second->AddRef(); *ppSamplerState = iter->second.Get(); return S_OK; } D3D11_SAMPLER_DESC samp{}; samp.AddressU = key.sClamp ? D3D11_TEXTURE_ADDRESS_CLAMP : D3D11_TEXTURE_ADDRESS_WRAP; samp.AddressV = key.tClamp ? D3D11_TEXTURE_ADDRESS_CLAMP : D3D11_TEXTURE_ADDRESS_WRAP; samp.AddressW = samp.AddressU; // Mali benefits from all clamps being the same, and this one is irrelevant. if (key.aniso) { samp.MaxAnisotropy = (float)(1 << g_Config.iAnisotropyLevel); } else { samp.MaxAnisotropy = 1.0f; } int filterKey = ((int)key.minFilt << 2) | ((int)key.magFilt << 1) | ((int)key.mipFilt); static const D3D11_FILTER filters[8] = { D3D11_FILTER_MIN_MAG_MIP_POINT, D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR, D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, D3D11_FILTER_MIN_POINT_MAG_MIP_LINEAR, D3D11_FILTER_MIN_LINEAR_MAG_MIP_POINT, D3D11_FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT, D3D11_FILTER_MIN_MAG_MIP_LINEAR, }; // Only switch to aniso if linear min and mag are set. if (key.aniso && key.magFilt != 0 && key.minFilt != 0) samp.Filter = D3D11_FILTER_ANISOTROPIC; else samp.Filter = filters[filterKey]; // Can't set MaxLOD on Feature Level <= 9_3. if (device->GetFeatureLevel() <= D3D_FEATURE_LEVEL_9_3) { samp.MaxLOD = FLT_MAX; samp.MinLOD = -FLT_MAX; samp.MipLODBias = 0.0f; } else { samp.MaxLOD = key.maxLevel / 256.0f; samp.MinLOD = key.minLevel / 256.0f; samp.MipLODBias = key.lodBias / 256.0f; } samp.ComparisonFunc = D3D11_COMPARISON_NEVER; for (int i = 0; i < 4; i++) { samp.BorderColor[i] = 1.0f; } ASSERT_SUCCESS(device->CreateSamplerState(&samp, ppSamplerState)); cache_[key] = *ppSamplerState; return S_OK; } TextureCacheD3D11::TextureCacheD3D11(Draw::DrawContext *draw, Draw2D *draw2D) : TextureCacheCommon(draw, draw2D) { device_ = (ID3D11Device *)draw->GetNativeObject(Draw::NativeObject::DEVICE); context_ = (ID3D11DeviceContext *)draw->GetNativeObject(Draw::NativeObject::CONTEXT); lastBoundTexture_ = D3D11_INVALID_TEX; InitDeviceObjects(); } TextureCacheD3D11::~TextureCacheD3D11() { Clear(true); DestroyDeviceObjects(); } void TextureCacheD3D11::InitDeviceObjects() { D3D11_BUFFER_DESC desc{ sizeof(DepthPushConstants), D3D11_USAGE_DYNAMIC, D3D11_BIND_CONSTANT_BUFFER, D3D11_CPU_ACCESS_WRITE }; HRESULT hr = device_->CreateBuffer(&desc, nullptr, &depalConstants_); _dbg_assert_(SUCCEEDED(hr)); D3D11_SAMPLER_DESC sampler_desc{}; sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; sampler_desc.ComparisonFunc = D3D11_COMPARISON_NEVER; for (int i = 0; i < 4; i++) sampler_desc.BorderColor[i] = 1.0f; sampler_desc.MinLOD = -FLT_MAX; sampler_desc.MaxLOD = FLT_MAX; sampler_desc.MipLODBias = 0.0f; sampler_desc.MaxAnisotropy = 1; sampler_desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; sampler_desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; sampler_desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; ASSERT_SUCCESS(device_->CreateSamplerState(&sampler_desc, &samplerPoint2DClamp_)); sampler_desc.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; ASSERT_SUCCESS(device_->CreateSamplerState(&sampler_desc, &samplerLinear2DClamp_)); } void TextureCacheD3D11::DestroyDeviceObjects() { depalConstants_.Reset(); samplerPoint2DClamp_.Reset(); samplerLinear2DClamp_.Reset(); lastBoundTexture_ = D3D11_INVALID_TEX; samplerCache_.Destroy(); } void TextureCacheD3D11::DeviceLost() { TextureCacheCommon::DeviceLost(); DestroyDeviceObjects(); draw_ = nullptr; } void TextureCacheD3D11::DeviceRestore(Draw::DrawContext *draw) { TextureCacheCommon::DeviceRestore(draw); InitDeviceObjects(); } void TextureCacheD3D11::SetFramebufferManager(FramebufferManagerD3D11 *fbManager) { framebufferManager_ = fbManager; } void TextureCacheD3D11::ReleaseTexture(TexCacheEntry *entry, bool delete_them) { ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr; ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)entry->textureView; if (texture) { texture->Release(); entry->texturePtr = nullptr; } if (view) { view->Release(); entry->textureView = nullptr; } } void TextureCacheD3D11::ForgetLastTexture() { lastBoundTexture_ = D3D11_INVALID_TEX; ID3D11ShaderResourceView *nullTex[4]{}; context_->PSSetShaderResources(0, 4, nullTex); } void TextureCacheD3D11::BindTexture(TexCacheEntry *entry, bool flatZ) { if (!entry) { ID3D11ShaderResourceView *textureView = nullptr; context_->PSSetShaderResources(0, 1, &textureView); return; } ID3D11ShaderResourceView *textureView = DxView(entry); if (textureView != lastBoundTexture_) { context_->PSSetShaderResources(0, 1, &textureView); lastBoundTexture_ = textureView; } int maxLevel = (entry->status & TexStatus::NO_MIPS) ? 0 : entry->maxLevel; SamplerCacheKey samplerKey = GetSamplingParams(maxLevel, entry, flatZ); ComPtr state; samplerCache_.GetOrCreateSampler(device_, samplerKey, &state); context_->PSSetSamplers(0, 1, state.GetAddressOf()); } void TextureCacheD3D11::ApplySamplingParams(const SamplerCacheKey &key) { ComPtr state; samplerCache_.GetOrCreateSampler(device_, key, &state); context_->PSSetSamplers(0, 1, state.GetAddressOf()); } void TextureCacheD3D11::Unbind() { ForgetLastTexture(); } void TextureCacheD3D11::BindAsClutTexture(Draw::Texture *tex, bool smooth) { ID3D11ShaderResourceView *clutTexture = (ID3D11ShaderResourceView *)draw_->GetNativeObject(Draw::NativeObject::TEXTURE_VIEW, tex); context_->PSSetShaderResources(TEX_SLOT_CLUT, 1, &clutTexture); context_->PSSetSamplers(3, 1, smooth ? samplerLinear2DClamp_.GetAddressOf() : samplerPoint2DClamp_.GetAddressOf()); } void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { BuildTexturePlan plan; if (!PrepareBuildTexture(plan, entry)) { // We're screwed? return; } DXGI_FORMAT dstFmt = GetDestFormat(GETextureFormat(entry->format), gstate.getClutPaletteFormat()); if (plan.doReplace) { dstFmt = ToDXGIFormat(plan.replaced->Format()); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = DXGI_FORMAT_R8G8B8A8_UNORM; } else if (plan.decodeToClut8) { dstFmt = DXGI_FORMAT_R8_UNORM; } int levels; ID3D11ShaderResourceView *view; ID3D11Resource *texture = DxTex(entry); _assert_(texture == nullptr); // The PSP only supports 8 mip levels, but we support more in the texture replacer. 20 will never run out. D3D11_SUBRESOURCE_DATA subresData[20]{}; if (plan.depth == 1) { // We don't yet have mip generation, so clamp the number of levels to the ones we can load directly. levels = std::min(plan.levelsToCreate, plan.levelsToLoad); } else { levels = plan.depth; } Draw::DataFormat texFmt = FromD3D11Format(dstFmt); for (int i = 0; i < levels; i++) { int srcLevel = (i == 0) ? plan.baseLevelSrc : i; int mipWidth; int mipHeight; plan.GetMipSize(i, &mipWidth, &mipHeight); u8 *data = nullptr; int stride = 0; int dataSize; if (plan.doReplace) { int blockSize = 0; if (Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), &blockSize)) { stride = ((mipWidth + 3) & ~3) * blockSize / 4; // Number of blocks * 4 * Size of a block / 4 dataSize = plan.replaced->GetLevelDataSizeAfterCopy(i); } else { int bpp = (int)Draw::DataFormatSizeInBytes(plan.replaced->Format()); stride = std::max(mipWidth * bpp, 16); dataSize = stride * mipHeight; } } else { int bpp = 0; if (plan.scaleFactor > 1) { bpp = 4; } else { bpp = dstFmt == DXGI_FORMAT_R8G8B8A8_UNORM ? 4 : 2; } stride = std::max(mipWidth * bpp, 16); dataSize = stride * mipHeight; } if (plan.depth == 1) { data = (u8 *)AllocateAlignedMemory(dataSize, 16); subresData[i].pSysMem = data; subresData[i].SysMemPitch = stride; subresData[i].SysMemSlicePitch = 0; } else { if (i == 0) { subresData[0].pSysMem = AllocateAlignedMemory(stride * mipHeight * plan.depth, 16); subresData[0].SysMemPitch = stride; subresData[0].SysMemSlicePitch = stride * mipHeight; } data = (uint8_t *)subresData[0].pSysMem + stride * mipHeight * i; } if (!data) { ERROR_LOG(Log::G3D, "Ran out of RAM trying to allocate a temporary texture upload buffer (%dx%d)", mipWidth, mipHeight); return; } LoadTextureLevel(*entry, data, 0, stride, plan, srcLevel, texFmt, TexDecodeFlags{}); } int tw; int th; plan.GetMipSize(0, &tw, &th); if (tw > 16384) tw = 16384; if (th > 16384) th = 16384; // NOTE: For block-compressed textures, we'll force the size up to the closest 4x4. This is due to an // unfortunate restriction in D3D11 (and early D3D12). We'll warn about it in the log to give texture pack // authors notice to fix it. if (plan.doReplace && Draw::DataFormatIsBlockCompressed(plan.replaced->Format(), nullptr)) { tw = (tw + 3) & ~3; th = (th + 3) & ~3; } if (plan.depth == 1) { // We don't yet have mip generation, so clamp the number of levels to the ones we can load directly. levels = std::min(plan.levelsToCreate, plan.levelsToLoad); ID3D11Texture2D *tex = nullptr; D3D11_TEXTURE2D_DESC desc{}; desc.CPUAccessFlags = 0; desc.Usage = D3D11_USAGE_DEFAULT; desc.ArraySize = 1; desc.SampleDesc.Count = 1; desc.Width = tw; desc.Height = th; desc.Format = dstFmt; desc.MipLevels = levels; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; ASSERT_SUCCESS(device_->CreateTexture2D(&desc, subresData, &tex)); texture = tex; } else { ID3D11Texture3D *tex = nullptr; D3D11_TEXTURE3D_DESC desc{}; desc.CPUAccessFlags = 0; desc.Usage = D3D11_USAGE_DEFAULT; desc.Width = tw; desc.Height = th; desc.Depth = plan.depth; desc.Format = dstFmt; desc.MipLevels = 1; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; ASSERT_SUCCESS(device_->CreateTexture3D(&desc, subresData, &tex)); texture = tex; levels = plan.depth; } ASSERT_SUCCESS(device_->CreateShaderResourceView(texture, nullptr, &view)); entry->texturePtr = texture; entry->textureView = view; for (int i = 0; i < 12; i++) { if (subresData[i].pSysMem) { FreeAlignedMemory((void *)subresData[i].pSysMem); } } // Signal that we support depth textures so use it as one. if (plan.depth > 1) { entry->status |= TexStatus::IS_3D; } if (levels == 1) { entry->status |= TexStatus::NO_MIPS; } else { entry->status &= ~TexStatus::NO_MIPS; } if (plan.doReplace) { entry->SetAlphaStatus(plan.replaced->AlphaStatus()); } } DXGI_FORMAT GetClutDestFormatD3D11(GEPaletteFormat format) { switch (format) { case GE_CMODE_16BIT_ABGR4444: return DXGI_FORMAT_B4G4R4A4_UNORM; case GE_CMODE_16BIT_ABGR5551: return DXGI_FORMAT_B5G5R5A1_UNORM; case GE_CMODE_16BIT_BGR5650: return DXGI_FORMAT_B5G6R5_UNORM; case GE_CMODE_32BIT_ABGR8888: return DXGI_FORMAT_R8G8B8A8_UNORM; } // Should never be here ! return DXGI_FORMAT_R8G8B8A8_UNORM; } DXGI_FORMAT TextureCacheD3D11::GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const { if (!gstate_c.Use(GPU_USE_16BIT_FORMATS)) { return DXGI_FORMAT_R8G8B8A8_UNORM; } switch (format) { case GE_TFMT_CLUT4: case GE_TFMT_CLUT8: case GE_TFMT_CLUT16: case GE_TFMT_CLUT32: return GetClutDestFormatD3D11(clutFormat); case GE_TFMT_4444: return DXGI_FORMAT_B4G4R4A4_UNORM; case GE_TFMT_5551: return DXGI_FORMAT_B5G5R5A1_UNORM; case GE_TFMT_5650: return DXGI_FORMAT_B5G6R5_UNORM; case GE_TFMT_8888: case GE_TFMT_DXT1: case GE_TFMT_DXT3: case GE_TFMT_DXT5: default: return DXGI_FORMAT_R8G8B8A8_UNORM; } } bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level, bool *isFramebuffer) { SetTexture(); if (!nextTexture_) { return GetCurrentFramebufferTextureDebug(buffer, isFramebuffer); } // Apply texture may need to rebuild the texture if we're about to render, or bind a framebuffer. TexCacheEntry *entry = nextTexture_; ApplyTexture(true, false); ID3D11Texture2D *texture = (ID3D11Texture2D *)entry->texturePtr; if (!texture) return false; D3D11_TEXTURE2D_DESC desc; texture->GetDesc(&desc); int width = desc.Width >> level; int height = desc.Height >> level; switch (desc.Format) { case DXGI_FORMAT_R8G8B8A8_UNORM: buffer.Allocate(width, height, GPU_DBG_FORMAT_8888); break; case DXGI_FORMAT_B5G6R5_UNORM: buffer.Allocate(width, height, GPU_DBG_FORMAT_565); break; case DXGI_FORMAT_B4G4R4A4_UNORM: buffer.Allocate(width, height, GPU_DBG_FORMAT_4444); break; case DXGI_FORMAT_B5G5R5A1_UNORM: buffer.Allocate(width, height, GPU_DBG_FORMAT_5551); break; case DXGI_FORMAT_R8_UNORM: buffer.Allocate(width, height, GPU_DBG_FORMAT_8BIT); break; default: return false; } desc.BindFlags = 0; desc.Usage = D3D11_USAGE_STAGING; desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; ComPtr stagingCopy; device_->CreateTexture2D(&desc, nullptr, &stagingCopy); if (!stagingCopy) return false; context_->CopyResource(stagingCopy.Get(), texture); D3D11_MAPPED_SUBRESOURCE map; if (FAILED(context_->Map(stagingCopy.Get(), level, D3D11_MAP_READ, 0, &map))) { return false; } int bufferRowSize = buffer.PixelSize() * width; for (int y = 0; y < height; y++) { memcpy(buffer.GetData() + bufferRowSize * y, (const uint8_t *)map.pData + map.RowPitch * y, bufferRowSize); } context_->Unmap(stagingCopy.Get(), level); *isFramebuffer = false; return true; } void *TextureCacheD3D11::GetNativeTextureView(const TexCacheEntry *entry, bool flat) const { ID3D11ShaderResourceView *textureView = DxView(entry); return (void *)textureView; }