diff options
| author | Tilka <tilkax@gmail.com> | 2019-02-26 04:21:05 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-26 04:21:05 +0000 |
| commit | 6ce7f44b8a0bb3b95d3f12ebffbef7a71a443fdd (patch) | |
| tree | 98380ce3a3284c52597a6fba86589c847219ecd5 /Source/Core/VideoCommon/TextureCacheBase.cpp | |
| parent | 19673ce79af814ca3b77d7f8ea60458e564bfa51 (diff) | |
| parent | f039149198657c1891e1c6462ed30c31ed4b8486 (diff) | |
Merge pull request #7753 from stenzek/videocommon-all-the-things
Move a significant amount of video backend logic to VideoCommon
Diffstat (limited to 'Source/Core/VideoCommon/TextureCacheBase.cpp')
| -rw-r--r-- | Source/Core/VideoCommon/TextureCacheBase.cpp | 648 |
1 files changed, 504 insertions, 144 deletions
diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index a42438e0b1..3beaeedbf8 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -28,16 +28,21 @@ #include "Core/FifoPlayer/FifoRecorder.h" #include "Core/HW/Memmap.h" +#include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/BPMemory.h" -#include "VideoCommon/Debugger.h" -#include "VideoCommon/FramebufferManagerBase.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/HiresTextures.h" +#include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/SamplerCommon.h" +#include "VideoCommon/ShaderCache.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureConversionShader.h" +#include "VideoCommon/TextureConverterShaderGen.h" #include "VideoCommon/TextureDecoder.h" +#include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -50,8 +55,9 @@ std::unique_ptr<TextureCacheBase> g_texture_cache; std::bitset<8> TextureCacheBase::valid_bind_points; -TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex) - : texture(std::move(tex)) +TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex, + std::unique_ptr<AbstractFramebuffer> fb) + : texture(std::move(tex)), framebuffer(std::move(fb)) { } @@ -88,6 +94,25 @@ TextureCacheBase::TextureCacheBase() InvalidateAllBindPoints(); } +TextureCacheBase::~TextureCacheBase() +{ + HiresTexture::Shutdown(); + Invalidate(); + Common::FreeAlignedMemory(temp); + temp = nullptr; +} + +bool TextureCacheBase::Initialize() +{ + if (!CreateUtilityTextures()) + { + PanicAlert("Failed to create utility textures."); + return false; + } + + return true; +} + void TextureCacheBase::Invalidate() { FlushEFBCopies(); @@ -107,14 +132,6 @@ void TextureCacheBase::Invalidate() texture_pool.clear(); } -TextureCacheBase::~TextureCacheBase() -{ - HiresTexture::Shutdown(); - Invalidate(); - Common::FreeAlignedMemory(temp); - temp = nullptr; -} - void TextureCacheBase::OnConfigChanged(VideoConfig& config) { if (config.bHiresTextures != backup_config.hires_textures || @@ -138,14 +155,6 @@ void TextureCacheBase::OnConfigChanged(VideoConfig& config) g_ActiveConfig.bTexFmtOverlayCenter); } - if ((config.stereo_mode != StereoMode::Off) != backup_config.stereo_3d || - config.bStereoEFBMonoDepth != backup_config.efb_mono_depth) - { - g_texture_cache->DeleteShaders(); - if (!g_texture_cache->CompileShaders()) - PanicAlert("Failed to recompile one or more texture conversion shaders."); - } - SetBackupConfig(config); } @@ -242,7 +251,7 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, u8* palette, TLUTForma { TextureConfig new_config = entry->texture->GetConfig(); new_config.levels = 1; - new_config.rendertarget = true; + new_config.flags |= AbstractTextureFlag_RenderTarget; TCacheEntry* decoded_entry = AllocateCacheEntry(new_config); if (!decoded_entry) @@ -278,29 +287,27 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntry* e return; } - TextureConfig newconfig; - newconfig.width = new_width; - newconfig.height = new_height; - newconfig.layers = entry->GetNumLayers(); - newconfig.rendertarget = true; - - std::unique_ptr<AbstractTexture> new_texture = AllocateTexture(newconfig); - if (new_texture) - { - new_texture->ScaleRectangleFromTexture(entry->texture.get(), - entry->texture->GetConfig().GetRect(), - new_texture->GetConfig().GetRect()); - entry->texture.swap(new_texture); - - auto config = new_texture->GetConfig(); - // At this point new_texture has the old texture in it, - // we can potentially reuse this, so let's move it back to the pool - texture_pool.emplace(config, TexPoolEntry(std::move(new_texture))); - } - else + const TextureConfig newconfig(new_width, new_height, 1, entry->GetNumLayers(), 1, + AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget); + std::optional<TexPoolEntry> new_texture = AllocateTexture(newconfig); + if (!new_texture) { - ERROR_LOG(VIDEO, "Scaling failed"); + ERROR_LOG(VIDEO, "Scaling failed due to texture allocation failure"); + return; } + + // No need to convert the coordinates here since they'll be the same. + g_renderer->ScaleTexture(new_texture->framebuffer.get(), + new_texture->texture->GetConfig().GetRect(), entry->texture.get(), + entry->texture->GetConfig().GetRect()); + entry->texture.swap(new_texture->texture); + entry->framebuffer.swap(new_texture->framebuffer); + + // At this point new_texture has the old texture in it, + // we can potentially reuse this, so let's move it back to the pool + auto config = new_texture->texture->GetConfig(); + texture_pool.emplace( + config, TexPoolEntry(std::move(new_texture->texture), std::move(new_texture->framebuffer))); } TextureCacheBase::TCacheEntry* @@ -383,6 +390,17 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* pale dst_y = 0; } + // If the source rectangle is outside of what we actually have in VRAM, skip the copy. + // The backend doesn't do any clamping, so if we don't, we'd pass out-of-range coordinates + // to the graphics driver, which can cause GPU resets. + if (static_cast<u32>(src_x) >= entry->native_width || + static_cast<u32>(src_y) >= entry->native_height || + static_cast<u32>(dst_x) >= entry_to_update->native_width || + static_cast<u32>(dst_y) >= entry_to_update->native_height) + { + continue; + } + u32 copy_width = std::min(entry->native_width - src_x, entry_to_update->native_width - dst_x); u32 copy_height = @@ -482,12 +500,79 @@ static u32 CalculateLevelSize(u32 level_0_size, u32 level) return std::max(level_0_size >> level, 1u); } +static void SetSamplerState(u32 index, float custom_tex_scale, bool custom_tex, + bool has_arbitrary_mips) +{ + const FourTexUnits& tex = bpmem.tex[index / 4]; + const TexMode0& tm0 = tex.texMode0[index % 4]; + + SamplerState state = {}; + state.Generate(bpmem, index); + + // Force texture filtering config option. + if (g_ActiveConfig.bForceFiltering) + { + state.min_filter = SamplerState::Filter::Linear; + state.mag_filter = SamplerState::Filter::Linear; + state.mipmap_filter = SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0) ? + SamplerState::Filter::Linear : + SamplerState::Filter::Point; + } + + // Custom textures may have a greater number of mips + if (custom_tex) + state.max_lod = 255; + + // Anisotropic filtering option. + if (g_ActiveConfig.iMaxAnisotropy != 0 && !SamplerCommon::IsBpTexMode0PointFiltering(tm0)) + { + // https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt + // For predictable results on all hardware/drivers, only use one of: + // GL_LINEAR + GL_LINEAR (No Mipmaps [Bilinear]) + // GL_LINEAR + GL_LINEAR_MIPMAP_LINEAR (w/ Mipmaps [Trilinear]) + // Letting the game set other combinations will have varying arbitrary results; + // possibly being interpreted as equal to bilinear/trilinear, implicitly + // disabling anisotropy, or changing the anisotropic algorithm employed. + state.min_filter = SamplerState::Filter::Linear; + state.mag_filter = SamplerState::Filter::Linear; + if (SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) + state.mipmap_filter = SamplerState::Filter::Linear; + state.anisotropic_filtering = 1; + } + else + { + state.anisotropic_filtering = 0; + } + + if (has_arbitrary_mips && SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) + { + // Apply a secondary bias calculated from the IR scale to pull inwards mipmaps + // that have arbitrary contents, eg. are used for fog effects where the + // distance they kick in at is important to preserve at any resolution. + // Correct this with the upscaling factor of custom textures. + s64 lod_offset = std::log2(g_renderer->GetEFBScale() / custom_tex_scale) * 256.f; + state.lod_bias = MathUtil::Clamp<s64>(state.lod_bias + lod_offset, -32768, 32767); + + // Anisotropic also pushes mips farther away so it cannot be used either + state.anisotropic_filtering = 0; + } + + g_renderer->SetSamplerState(index, state); +} + void TextureCacheBase::BindTextures() { for (u32 i = 0; i < bound_textures.size(); i++) { - if (IsValidBindPoint(i) && bound_textures[i]) - g_renderer->SetTexture(i, bound_textures[i]->texture.get()); + const TCacheEntry* tentry = bound_textures[i]; + if (IsValidBindPoint(i) && tentry) + { + g_renderer->SetTexture(i, tentry->texture.get()); + PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height); + + const float custom_tex_scale = tentry->GetWidth() / float(tentry->native_width); + SetSamplerState(i, custom_tex_scale, tentry->is_custom_tex, tentry->has_arbitrary_mips); + } } } @@ -668,8 +753,6 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) entry->frameCount = FRAMECOUNT_INVALID; bound_textures[stage] = entry; - GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true); - // We need to keep track of invalided textures until they have actually been replaced or // re-loaded valid_bind_points.set(stage); @@ -957,25 +1040,17 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo // banks, and if we're doing an copy we may as well just do the whole thing on the CPU, since // there's no conversion between formats. In the future this could be extended with a separate // shader, however. - bool decode_on_gpu = !hires_tex && g_ActiveConfig.UseGPUTextureDecoding() && - g_texture_cache->SupportsGPUTextureDecode(texformat, tlutfmt) && - !(from_tmem && texformat == TextureFormat::RGBA8); + const bool decode_on_gpu = !hires_tex && g_ActiveConfig.UseGPUTextureDecoding() && + !(from_tmem && texformat == TextureFormat::RGBA8); // create the entry/texture - TextureConfig config; - config.width = width; - config.height = height; - config.levels = texLevels; - config.format = hires_tex ? hires_tex->GetFormat() : AbstractTextureFormat::RGBA8; - - ArbitraryMipmapDetector arbitrary_mip_detector; - + const TextureConfig config(width, height, texLevels, 1, 1, + hires_tex ? hires_tex->GetFormat() : AbstractTextureFormat::RGBA8, 0); TCacheEntry* entry = AllocateCacheEntry(config); - GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); - if (!entry) return nullptr; + ArbitraryMipmapDetector arbitrary_mip_detector; const u8* tlut = &texMem[tlutaddr]; if (hires_tex) { @@ -989,14 +1064,10 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo if (!hires_tex) { - if (decode_on_gpu) - { - u32 row_stride = bytes_per_block * (expandedWidth / bsw); - g_texture_cache->DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width, - height, expandedWidth, expandedHeight, row_stride, tlut, - tlutfmt); - } - else + if (!decode_on_gpu || + !DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width, height, + expandedWidth, expandedHeight, bytes_per_block * (expandedWidth / bsw), + tlut, tlutfmt)) { size_t decoded_texture_size = expandedWidth * sizeof(u32) * expandedHeight; @@ -1089,20 +1160,16 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo const u32 expanded_mip_height = Common::AlignUp(mip_height, bsh); const u8*& mip_src_data = from_tmem ? ((level % 2) ? ptr_odd : ptr_even) : src_data; - size_t mip_size = + const u32 mip_size = TexDecoder_GetTextureSizeInBytes(expanded_mip_width, expanded_mip_height, texformat); - if (decode_on_gpu) - { - u32 row_stride = bytes_per_block * (expanded_mip_width / bsw); - g_texture_cache->DecodeTextureOnGPU(entry, level, mip_src_data, mip_size, texformat, - mip_width, mip_height, expanded_mip_width, - expanded_mip_height, row_stride, tlut, tlutfmt); - } - else + if (!decode_on_gpu || + !DecodeTextureOnGPU(entry, level, mip_src_data, mip_size, texformat, mip_width, + mip_height, expanded_mip_width, expanded_mip_height, + bytes_per_block * (expanded_mip_width / bsw), tlut, tlutfmt)) { // No need to call CheckTempSize here, as the whole buffer is preallocated at the beginning - size_t decoded_mip_size = expanded_mip_width * sizeof(u32) * expanded_mip_height; + const u32 decoded_mip_size = expanded_mip_width * sizeof(u32) * expanded_mip_height; TexDecoder_Decode(dst_buffer, mip_src_data, expanded_mip_width, expanded_mip_height, texformat, tlut, tlutfmt); entry->texture->Load(level, mip_width, mip_height, expanded_mip_width, dst_buffer, @@ -1133,6 +1200,8 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo entry = DoPartialTextureUpdates(iter->second, &texMem[tlutaddr], tlutfmt); + // This should only be needed if the texture was updated, or used GPU decoding. + entry->texture->FinishedRendering(); return entry; } @@ -1300,7 +1369,7 @@ TextureCacheBase::GetTextureFromOverlappingTextures(const TextureLookupInformati // or as a container for overlapping textures, never need to be combined // with other textures TCacheEntry* stitched_entry = - CreateNormalTexture(tex_info, FramebufferManagerBase::GetEFBLayers()); + CreateNormalTexture(tex_info, g_framebuffer_manager->GetEFBLayers()); stitched_entry->may_have_overlapping_textures = false; // It is possible that some of the overlapping textures overlap each other. @@ -1385,6 +1454,17 @@ TextureCacheBase::GetTextureFromOverlappingTextures(const TextureLookupInformati dst_y = 0; } + // If the source rectangle is outside of what we actually have in VRAM, skip the copy. + // The backend doesn't do any clamping, so if we don't, we'd pass out-of-range coordinates + // to the graphics driver, which can cause GPU resets. + if (static_cast<u32>(src_x) >= entry->native_width || + static_cast<u32>(src_y) >= entry->native_height || + static_cast<u32>(dst_x) >= stitched_entry->native_width || + static_cast<u32>(dst_y) >= stitched_entry->native_height) + { + continue; + } + u32 copy_width = std::min(entry->native_width - src_x, stitched_entry->native_width - dst_x); u32 copy_height = std::min(entry->native_height - src_y, stitched_entry->native_height - dst_y); @@ -1450,6 +1530,7 @@ TextureCacheBase::GetTextureFromOverlappingTextures(const TextureLookupInformati return nullptr; } + stitched_entry->texture->FinishedRendering(); return stitched_entry; } @@ -1457,17 +1538,10 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::CreateNormalTexture(const TextureLookupInformation& tex_info, u32 layers) { // create the entry/texture - TextureConfig config; - config.width = tex_info.native_width; - config.height = tex_info.native_height; - config.levels = tex_info.computed_levels; - config.format = AbstractTextureFormat::RGBA8; - config.rendertarget = true; - config.layers = layers; - + const TextureConfig config(tex_info.native_width, tex_info.native_height, + tex_info.computed_levels, layers, 1, AbstractTextureFormat::RGBA8, + AbstractTextureFlag_RenderTarget); TCacheEntry* entry = AllocateCacheEntry(config); - GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); - if (!entry) return nullptr; @@ -1500,15 +1574,15 @@ TextureCacheBase::GetTextureFromMemory(const TextureLookupInformation& tex_info) // banks, and if we're doing an copy we may as well just do the whole thing on the CPU, since // there's no conversion between formats. In the future this could be extended with a separate // shader, however. - bool decode_on_gpu = g_ActiveConfig.UseGPUTextureDecoding() && - g_texture_cache->SupportsGPUTextureDecode(tex_info.full_format.texfmt, - tex_info.full_format.tlutfmt) && - !(tex_info.from_tmem && tex_info.full_format.texfmt == TextureFormat::RGBA8); + const bool decode_on_gpu = + g_ActiveConfig.UseGPUTextureDecoding() && + !(tex_info.from_tmem && tex_info.full_format.texfmt == TextureFormat::RGBA8); // Since it's coming from RAM, it can only have one layer (no stereo). TCacheEntry* entry = CreateNormalTexture(tex_info, 1); entry->may_have_overlapping_textures = false; LoadTextureLevelZeroFromMemory(entry, tex_info, decode_on_gpu); + entry->texture->FinishedRendering(); return entry; } @@ -1518,15 +1592,13 @@ void TextureCacheBase::LoadTextureLevelZeroFromMemory(TCacheEntry* entry_to_upda { const u8* tlut = &texMem[tex_info.tlut_address]; - if (decode_on_gpu) - { - u32 row_stride = tex_info.bytes_per_block * (tex_info.expanded_width / tex_info.block_width); - g_texture_cache->DecodeTextureOnGPU( - entry_to_update, 0, tex_info.src_data, tex_info.total_bytes, tex_info.full_format.texfmt, - tex_info.native_width, tex_info.native_height, tex_info.expanded_width, - tex_info.expanded_height, row_stride, tlut, tex_info.full_format.tlutfmt); - } - else + if (!decode_on_gpu || + DecodeTextureOnGPU(entry_to_update, 0, tex_info.src_data, tex_info.total_bytes, + tex_info.full_format.texfmt, tex_info.native_width, tex_info.native_height, + tex_info.expanded_width, tex_info.expanded_height, + tex_info.bytes_per_block * + (tex_info.expanded_width / tex_info.block_width), + tlut, tex_info.full_format.tlutfmt)) { size_t decoded_texture_size = tex_info.expanded_width * sizeof(u32) * tex_info.expanded_height; CheckTempSize(decoded_texture_size); @@ -1547,12 +1619,12 @@ void TextureCacheBase::LoadTextureLevelZeroFromMemory(TCacheEntry* entry_to_upda } } -TextureCacheBase::CopyFilterCoefficientArray TextureCacheBase::GetRAMCopyFilterCoefficients( - const CopyFilterCoefficients::Values& coefficients) const +EFBCopyFilterCoefficients +TextureCacheBase::GetRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients) { // To simplify the backend, we precalculate the three coefficients in common. Coefficients 0, 1 // are for the row above, 2, 3, 4 are for the current pixel, and 5, 6 are for the row below. - return {{ + return EFBCopyFilterCoefficients{ static_cast<float>(static_cast<u32>(coefficients[0]) + static_cast<u32>(coefficients[1])) / 64.0f, static_cast<float>(static_cast<u32>(coefficients[2]) + static_cast<u32>(coefficients[3]) + @@ -1560,31 +1632,31 @@ TextureCacheBase::CopyFilterCoefficientArray TextureCacheBase::GetRAMCopyFilterC 64.0f, static_cast<float>(static_cast<u32>(coefficients[5]) + static_cast<u32>(coefficients[6])) / 64.0f, - }}; + }; } -TextureCacheBase::CopyFilterCoefficientArray TextureCacheBase::GetVRAMCopyFilterCoefficients( - const CopyFilterCoefficients::Values& coefficients) const +EFBCopyFilterCoefficients +TextureCacheBase::GetVRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients) { // If the user disables the copy filter, only apply it to the VRAM copy. // This way games which are sensitive to changes to the RAM copy of the XFB will be unaffected. - CopyFilterCoefficientArray res = GetRAMCopyFilterCoefficients(coefficients); + EFBCopyFilterCoefficients res = GetRAMCopyFilterCoefficients(coefficients); if (!g_ActiveConfig.bDisableCopyFilter) return res; // Disabling the copy filter in options should not ignore the values the game sets completely, // as some games use the filter coefficients to control the brightness of the screen. Instead, // add all coefficients to the middle sample, so the deflicker/vertical filter has no effect. - res[1] += res[0] + res[2]; - res[0] = 0; - res[2] = 0; + res.middle = res.upper + res.middle + res.lower; + res.upper = 0.0f; + res.lower = 0.0f; return res; } -bool TextureCacheBase::NeedsCopyFilterInShader(const CopyFilterCoefficientArray& coefficients) const +bool TextureCacheBase::NeedsCopyFilterInShader(const EFBCopyFilterCoefficients& coefficients) { // If the top/bottom coefficients are zero, no point sampling/blending from these rows. - return coefficients[0] != 0 || coefficients[2] != 0; + return coefficients.upper != 0 || coefficients.lower != 0; } void TextureCacheBase::CopyRenderTargetToTexture( @@ -1726,12 +1798,8 @@ void TextureCacheBase::CopyRenderTargetToTexture( if (copy_to_vram) { // create the texture - TextureConfig config; - config.rendertarget = true; - config.width = scaled_tex_w; - config.height = scaled_tex_h; - config.layers = FramebufferManagerBase::GetEFBLayers(); - + const TextureConfig config(scaled_tex_w, scaled_tex_h, 1, g_framebuffer_manager->GetEFBLayers(), + 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget); entry = AllocateCacheEntry(config); if (entry) { @@ -1776,7 +1844,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( if (copy_to_ram) { - CopyFilterCoefficientArray coefficients = GetRAMCopyFilterCoefficients(filter_coefficients); + EFBCopyFilterCoefficients coefficients = GetRAMCopyFilterCoefficients(filter_coefficients); PEControl::PixelFormat srcFormat = bpmem.zcontrol.pixel_format; EFBCopyParams format(srcFormat, dstFormat, is_depth_copy, isIntensity, NeedsCopyFilterInShader(coefficients)); @@ -1916,11 +1984,6 @@ void TextureCacheBase::FlushEFBCopies() m_pending_efb_copies.clear(); } -TextureConfig TextureCacheBase::GetEncodingTextureConfig() -{ - return TextureConfig(EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, true); -} - void TextureCacheBase::WriteEFBCopyToRAM(u8* dst_ptr, u32 width, u32 height, u32 stride, std::unique_ptr<AbstractStagingTexture> staging_texture) { @@ -1979,8 +2042,8 @@ std::unique_ptr<AbstractStagingTexture> TextureCacheBase::GetEFBCopyStagingTextu return ptr; } - std::unique_ptr<AbstractStagingTexture> tex = - g_renderer->CreateStagingTexture(StagingTextureType::Readback, GetEncodingTextureConfig()); + std::unique_ptr<AbstractStagingTexture> tex = g_renderer->CreateStagingTexture( + StagingTextureType::Readback, m_efb_encoding_texture->GetConfig()); if (!tex) WARN_LOG(VIDEO, "Failed to create EFB copy staging texture"); @@ -2037,37 +2100,50 @@ void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_ TextureCacheBase::TCacheEntry* TextureCacheBase::AllocateCacheEntry(const TextureConfig& config) { - std::unique_ptr<AbstractTexture> texture = AllocateTexture(config); - - if (!texture) - { + std::optional<TexPoolEntry> alloc = AllocateTexture(config); + if (!alloc) return nullptr; - } - TCacheEntry* cacheEntry = new TCacheEntry(std::move(texture)); + + TCacheEntry* cacheEntry = + new TCacheEntry(std::move(alloc->texture), std::move(alloc->framebuffer)); cacheEntry->textures_by_hash_iter = textures_by_hash.end(); cacheEntry->id = last_entry_id++; return cacheEntry; } -std::unique_ptr<AbstractTexture> TextureCacheBase::AllocateTexture(const TextureConfig& config) +std::optional<TextureCacheBase::TexPoolEntry> +TextureCacheBase::AllocateTexture(const TextureConfig& config) { TexPool::iterator iter = FindMatchingTextureFromPool(config); - std::unique_ptr<AbstractTexture> entry; if (iter != texture_pool.end()) { - entry = std::move(iter->second.texture); + auto entry = std::move(iter->second); texture_pool.erase(iter); + return std::move(entry); } - else + + std::unique_ptr<AbstractTexture> texture = g_renderer->CreateTexture(config); + if (!texture) { - entry = g_renderer->CreateTexture(config); - if (!entry) - return nullptr; + WARN_LOG(VIDEO, "Failed to allocate a %ux%ux%u texture", config.width, config.height, + config.layers); + return {}; + } - INCSTAT(stats.numTexturesCreated); + std::unique_ptr<AbstractFramebuffer> framebuffer; + if (config.IsRenderTarget()) + { + framebuffer = g_renderer->CreateFramebuffer(texture.get(), nullptr); + if (!framebuffer) + { + WARN_LOG(VIDEO, "Failed to allocate a %ux%ux%u framebuffer", config.width, config.height, + config.layers); + return {}; + } } - return entry; + INCSTAT(stats.numTexturesCreated); + return TexPoolEntry(std::move(texture), std::move(framebuffer)); } TextureCacheBase::TexPool::iterator @@ -2080,7 +2156,7 @@ TextureCacheBase::FindMatchingTextureFromPool(const TextureConfig& config) // As non-render-target textures are usually static, this should not matter much. auto range = texture_pool.equal_range(config); auto matching_iter = std::find_if(range.first, range.second, [](const auto& iter) { - return iter.first.rendertarget || iter.second.frameCount != FRAMECOUNT_INVALID; + return iter.first.IsRenderTarget() || iter.second.frameCount != FRAMECOUNT_INVALID; }); return matching_iter != range.second ? matching_iter : texture_pool.end(); } @@ -2171,7 +2247,8 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe } auto config = entry->texture->GetConfig(); - texture_pool.emplace(config, TexPoolEntry(std::move(entry->texture))); + texture_pool.emplace(config, + TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer))); // Don't delete if there's a pending EFB copy, as we need the TCacheEntry alive. if (!entry->pending_efb_copy) @@ -2180,6 +2257,283 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe return textures_by_address.erase(iter); } +bool TextureCacheBase::CreateUtilityTextures() +{ + constexpr TextureConfig encoding_texture_config( + EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget); + m_efb_encoding_texture = g_renderer->CreateTexture(encoding_texture_config); + if (!m_efb_encoding_texture) + return false; + + m_efb_encoding_framebuffer = g_renderer->CreateFramebuffer(m_efb_encoding_texture.get(), nullptr); + if (!m_efb_encoding_framebuffer) + return false; + + if (g_ActiveConfig.backend_info.bSupportsGPUTextureDecoding) + { + constexpr TextureConfig decoding_texture_config( + 1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage); + m_decoding_texture = g_renderer->CreateTexture(decoding_texture_config); + if (!m_decoding_texture) + return false; + } + + return true; +} + +void TextureCacheBase::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, + const EFBRectangle& src_rect, bool scale_by_half, + EFBCopyFormat dst_format, bool is_intensity, float gamma, + bool clamp_top, bool clamp_bottom, + const EFBCopyFilterCoefficients& filter_coefficients) +{ + // Flush EFB pokes first, as they're expected to be included. + g_framebuffer_manager->FlushEFBPokes(); + + // Get the pipeline which we will be using. If the compilation failed, this will be null. + const AbstractPipeline* copy_pipeline = + g_shader_cache->GetEFBCopyToVRAMPipeline(TextureConversionShaderGen::GetShaderUid( + dst_format, is_depth_copy, is_intensity, scale_by_half, + NeedsCopyFilterInShader(filter_coefficients))); + if (!copy_pipeline) + { + WARN_LOG(VIDEO, "Skipping EFB copy to VRAM due to missing pipeline."); + return; + } + + const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); + AbstractTexture* src_texture = + is_depth_copy ? g_framebuffer_manager->ResolveEFBDepthTexture(scaled_src_rect) : + g_framebuffer_manager->ResolveEFBColorTexture(scaled_src_rect); + + g_renderer->BeginUtilityDrawing(); + + // Fill uniform buffer. + struct Uniforms + { + float src_left, src_top, src_width, src_height; + float filter_coefficients[3]; + float gamma_rcp; + float clamp_top; + float clamp_bottom; + float pixel_height; + u32 padding; + }; + Uniforms uniforms; + const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); + const float rcp_efb_width = 1.0f / static_cast<float>(g_framebuffer_manager->GetEFBWidth()); + const float rcp_efb_height = 1.0f / static_cast<float>(g_framebuffer_manager->GetEFBHeight()); + uniforms.src_left = framebuffer_rect.left * rcp_efb_width; + uniforms.src_top = framebuffer_rect.top * rcp_efb_height; + uniforms.src_width = framebuffer_rect.GetWidth() * rcp_efb_width; + uniforms.src_height = framebuffer_rect.GetHeight() * rcp_efb_height; + uniforms.filter_coefficients[0] = filter_coefficients.upper; + uniforms.filter_coefficients[1] = filter_coefficients.middle; + uniforms.filter_coefficients[2] = filter_coefficients.lower; + uniforms.gamma_rcp = 1.0f / gamma; + uniforms.clamp_top = clamp_top ? framebuffer_rect.top * rcp_efb_height : 0.0f; + uniforms.clamp_bottom = clamp_bottom ? framebuffer_rect.bottom * rcp_efb_height : 1.0f; + uniforms.pixel_height = g_ActiveConfig.bCopyEFBScaled ? rcp_efb_height : 1.0f / EFB_HEIGHT; + uniforms.padding = 0; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + // Use the copy pipeline to render the VRAM copy. + g_renderer->SetAndDiscardFramebuffer(entry->framebuffer.get()); + g_renderer->SetViewportAndScissor(entry->framebuffer->GetRect()); + g_renderer->SetPipeline(copy_pipeline); + g_renderer->SetTexture(0, src_texture); + g_renderer->SetSamplerState(0, scale_by_half ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_renderer->Draw(0, 3); + g_renderer->EndUtilityDrawing(); + entry->texture->FinishedRendering(); +} + +void TextureCacheBase::CopyEFB(AbstractStagingTexture* 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 EFBCopyFilterCoefficients& filter_coefficients) +{ + // Flush EFB pokes first, as they're expected to be included. + g_framebuffer_manager->FlushEFBPokes(); + + // Get the pipeline which we will be using. If the compilation failed, this will be null. + const AbstractPipeline* copy_pipeline = g_shader_cache->GetEFBCopyToRAMPipeline(params); + if (!copy_pipeline) + { + WARN_LOG(VIDEO, "Skipping EFB copy to VRAM due to missing pipeline."); + return; + } + + const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); + AbstractTexture* src_texture = + params.depth ? g_framebuffer_manager->ResolveEFBDepthTexture(scaled_src_rect) : + g_framebuffer_manager->ResolveEFBColorTexture(scaled_src_rect); + + g_renderer->BeginUtilityDrawing(); + + // Fill uniform buffer. + struct Uniforms + { + std::array<s32, 4> position_uniform; + float y_scale; + float gamma_rcp; + float clamp_top; + float clamp_bottom; + float filter_coefficients[3]; + u32 padding; + }; + Uniforms encoder_params; + const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); + const float rcp_efb_height = 1.0f / static_cast<float>(g_framebuffer_manager->GetEFBHeight()); + encoder_params.position_uniform[0] = scaled_src_rect.left; + encoder_params.position_uniform[1] = scaled_src_rect.top; + encoder_params.position_uniform[2] = static_cast<s32>(native_width); + encoder_params.position_uniform[3] = scale_by_half ? 2 : 1; + encoder_params.y_scale = y_scale; + encoder_params.gamma_rcp = 1.0f / gamma; + encoder_params.clamp_top = clamp_top ? framebuffer_rect.top * rcp_efb_height : 0.0f; + encoder_params.clamp_bottom = clamp_bottom ? framebuffer_rect.bottom * rcp_efb_height : 1.0f; + encoder_params.filter_coefficients[0] = filter_coefficients.upper; + encoder_params.filter_coefficients[1] = filter_coefficients.middle; + encoder_params.filter_coefficients[2] = filter_coefficients.lower; + g_vertex_manager->UploadUtilityUniforms(&encoder_params, sizeof(encoder_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. + const bool linear_filter = + (scale_by_half && !params.depth) || g_renderer->GetEFBScale() != 1 || y_scale > 1.0f; + + // Because the shader uses gl_FragCoord and we read it back, we must render to the lower-left. + const u32 render_width = bytes_per_row / sizeof(u32); + const u32 render_height = num_blocks_y; + const auto encode_rect = MathUtil::Rectangle<int>(0, 0, render_width, render_height); + + // Render to GPU texture, and then copy to CPU-accessible texture. + g_renderer->SetAndDiscardFramebuffer(m_efb_encoding_framebuffer.get()); + g_renderer->SetViewportAndScissor(encode_rect); + g_renderer->SetPipeline(copy_pipeline); + g_renderer->SetTexture(0, src_texture); + g_renderer->SetSamplerState(0, linear_filter ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_renderer->Draw(0, 3); + dst->CopyFromTexture(m_efb_encoding_texture.get(), encode_rect, 0, 0, encode_rect); + g_renderer->EndUtilityDrawing(); + + // Flush if there's sufficient draws between this copy and the last. + g_vertex_manager->OnEFBCopyToRAM(); +} + +bool TextureCacheBase::ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, + const void* palette, TLUTFormat format) +{ + DEBUG_ASSERT(entry->texture->GetConfig().IsRenderTarget() && entry->framebuffer); + if (!g_ActiveConfig.backend_info.bSupportsPaletteConversion) + { + ERROR_LOG(VIDEO, "Backend does not support palette conversion!"); + return false; + } + + g_renderer->BeginUtilityDrawing(); + + const u32 palette_size = unconverted->format == TextureFormat::I4 ? 32 : 512; + u32 texel_buffer_offset; + if (!g_vertex_manager->UploadTexelBuffer(palette, palette_size, + TexelBufferFormat::TEXEL_BUFFER_FORMAT_R16_UINT, + &texel_buffer_offset)) + { + ERROR_LOG(VIDEO, "Texel buffer upload failed"); + return false; + } + + struct Uniforms + { + float multiplier; + u32 texel_buffer_offset; + u32 pad[2]; + }; + static_assert(std::is_standard_layout<Uniforms>::value); + Uniforms uniforms = {}; + uniforms.multiplier = unconverted->format == TextureFormat::I4 ? 15.0f : 255.0f; + uniforms.texel_buffer_offset = texel_buffer_offset; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + g_renderer->SetAndDiscardFramebuffer(entry->framebuffer.get()); + g_renderer->SetViewportAndScissor(entry->texture->GetRect()); + g_renderer->SetPipeline(g_shader_cache->GetPaletteConversionPipeline(format)); + g_renderer->SetTexture(1, unconverted->texture.get()); + g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState()); + g_renderer->Draw(0, 3); + g_renderer->EndUtilityDrawing(); + entry->texture->FinishedRendering(); + return true; +} + +bool TextureCacheBase::DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, + u32 data_size, TextureFormat format, u32 width, + u32 height, u32 aligned_width, u32 aligned_height, + u32 row_stride, const u8* palette, + TLUTFormat palette_format) +{ + const auto* info = TextureConversionShaderTiled::GetDecodingShaderInfo(format); + if (!info) + return false; + + const AbstractShader* shader = g_shader_cache->GetTextureDecodingShader(format, palette_format); + if (!shader) + return false; + + // Copy to GPU-visible buffer, aligned to the data type. + const u32 bytes_per_buffer_elem = + VertexManagerBase::GetTexelBufferElementSize(info->buffer_format); + + // Allocate space in stream buffer, and copy texture + palette across. + u32 src_offset = 0, palette_offset = 0; + if (info->palette_size > 0) + { + if (!g_vertex_manager->UploadTexelBuffer(data, data_size, info->buffer_format, &src_offset, + palette, info->palette_size, + TEXEL_BUFFER_FORMAT_R16_UINT, &palette_offset)) + { + return false; + } + } + else + { + if (!g_vertex_manager->UploadTexelBuffer(data, data_size, info->buffer_format, &src_offset)) + return false; + } + + // Set up uniforms. + struct Uniforms + { + u32 dst_width, dst_height; + u32 src_width, src_height; + u32 src_offset, src_row_stride; + u32 palette_offset, unused; + } uniforms = {width, height, aligned_width, + aligned_height, src_offset, row_stride / bytes_per_buffer_elem, + palette_offset}; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + g_renderer->SetComputeImageTexture(m_decoding_texture.get(), false, true); + + auto dispatch_groups = + TextureConversionShaderTiled::GetDispatchCount(info, aligned_width, aligned_height); + g_renderer->DispatchComputeShader(shader, dispatch_groups.first, dispatch_groups.second, 1); + + // Copy from decoding texture -> final texture + // This is because we don't want to have to create compute view for every layer + const auto copy_rect = entry->texture->GetConfig().GetMipRect(dst_level); + entry->texture->CopyRectangleFromTexture(m_decoding_texture.get(), copy_rect, 0, 0, copy_rect, 0, + dst_level); + entry->texture->FinishedRendering(); + return true; +} + u32 TextureCacheBase::TCacheEntry::BytesPerRow() const { const u32 blockW = TexDecoder_GetBlockWidthInTexels(format.texfmt); @@ -2272,3 +2626,9 @@ u64 TextureCacheBase::TCacheEntry::CalculateHash() const return temp_hash; } } + +TextureCacheBase::TexPoolEntry::TexPoolEntry(std::unique_ptr<AbstractTexture> tex, + std::unique_ptr<AbstractFramebuffer> fb) + : texture(std::move(tex)), framebuffer(std::move(fb)) +{ +} |
