diff --git a/CMakeModules/GenerateSCMRev.cmake b/CMakeModules/GenerateSCMRev.cmake index 31edeb63de..dd65cfe422 100644 --- a/CMakeModules/GenerateSCMRev.cmake +++ b/CMakeModules/GenerateSCMRev.cmake @@ -70,6 +70,7 @@ set(HASH_FILES "${VIDEO_CORE}/shader/decode/half_set.cpp" "${VIDEO_CORE}/shader/decode/half_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/hfma2.cpp" + "${VIDEO_CORE}/shader/decode/image.cpp" "${VIDEO_CORE}/shader/decode/integer_set.cpp" "${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/memory.cpp" diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 198b3fe076..2554add28e 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -44,6 +44,7 @@ add_custom_command(OUTPUT scm_rev.cpp "${VIDEO_CORE}/shader/decode/half_set.cpp" "${VIDEO_CORE}/shader/decode/half_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/hfma2.cpp" + "${VIDEO_CORE}/shader/decode/image.cpp" "${VIDEO_CORE}/shader/decode/integer_set.cpp" "${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp" "${VIDEO_CORE}/shader/decode/memory.cpp" @@ -74,6 +75,7 @@ add_library(common STATIC assert.h detached_tasks.cpp detached_tasks.h + binary_find.h bit_field.h bit_util.h cityhash.cpp diff --git a/src/common/alignment.h b/src/common/alignment.h index d94a2291f7..617b14d9b7 100644 --- a/src/common/alignment.h +++ b/src/common/alignment.h @@ -19,6 +19,12 @@ constexpr T AlignDown(T value, std::size_t size) { return static_cast(value - value % size); } +template +constexpr T AlignBits(T value, std::size_t align) { + static_assert(std::is_unsigned_v, "T must be an unsigned value."); + return static_cast((value + ((1ULL << align) - 1)) >> align << align); +} + template constexpr bool Is4KBAligned(T value) { static_assert(std::is_unsigned_v, "T must be an unsigned value."); diff --git a/src/common/binary_find.h b/src/common/binary_find.h new file mode 100644 index 0000000000..5cc523bf9c --- /dev/null +++ b/src/common/binary_find.h @@ -0,0 +1,21 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +namespace Common { + +template > +ForwardIt BinaryFind(ForwardIt first, ForwardIt last, const T& value, Compare comp = {}) { + // Note: BOTH type T and the type after ForwardIt is dereferenced + // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. + // This is stricter than lower_bound requirement (see above) + + first = std::lower_bound(first, last, value, comp); + return first != last && !comp(value, *first) ? first : last; +} + +} // namespace Common diff --git a/src/common/bit_util.h b/src/common/bit_util.h index d032df4131..6f7d5a9479 100644 --- a/src/common/bit_util.h +++ b/src/common/bit_util.h @@ -97,4 +97,48 @@ inline u32 CountTrailingZeroes64(u64 value) { } #endif +#ifdef _MSC_VER + +inline u32 MostSignificantBit32(const u32 value) { + unsigned long result; + _BitScanReverse(&result, value); + return static_cast(result); +} + +inline u32 MostSignificantBit64(const u64 value) { + unsigned long result; + _BitScanReverse64(&result, value); + return static_cast(result); +} + +#else + +inline u32 MostSignificantBit32(const u32 value) { + return 31U - static_cast(__builtin_clz(value)); +} + +inline u32 MostSignificantBit64(const u64 value) { + return 63U - static_cast(__builtin_clzll(value)); +} + +#endif + +inline u32 Log2Floor32(const u32 value) { + return MostSignificantBit32(value); +} + +inline u32 Log2Ceil32(const u32 value) { + const u32 log2_f = Log2Floor32(value); + return log2_f + ((value ^ (1U << log2_f)) != 0U); +} + +inline u32 Log2Floor64(const u64 value) { + return MostSignificantBit64(value); +} + +inline u32 Log2Ceil64(const u64 value) { + const u64 log2_f = static_cast(Log2Floor64(value)); + return static_cast(log2_f + ((value ^ (1ULL << log2_f)) != 0ULL)); +} + } // namespace Common diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index 8b0d34da6e..04ecac959b 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -4,6 +4,7 @@ #pragma once +#include #include #if !defined(ARCHITECTURE_x86_64) diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index f8b67cbe10..6839abe716 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -41,12 +41,12 @@ add_library(video_core STATIC renderer_opengl/gl_buffer_cache.h renderer_opengl/gl_device.cpp renderer_opengl/gl_device.h + renderer_opengl/gl_framebuffer_cache.cpp + renderer_opengl/gl_framebuffer_cache.h renderer_opengl/gl_global_cache.cpp renderer_opengl/gl_global_cache.h renderer_opengl/gl_rasterizer.cpp renderer_opengl/gl_rasterizer.h - renderer_opengl/gl_rasterizer_cache.cpp - renderer_opengl/gl_rasterizer_cache.h renderer_opengl/gl_resource_manager.cpp renderer_opengl/gl_resource_manager.h renderer_opengl/gl_sampler_cache.cpp @@ -67,6 +67,8 @@ add_library(video_core STATIC renderer_opengl/gl_state.h renderer_opengl/gl_stream_buffer.cpp renderer_opengl/gl_stream_buffer.h + renderer_opengl/gl_texture_cache.cpp + renderer_opengl/gl_texture_cache.h renderer_opengl/maxwell_to_gl.h renderer_opengl/renderer_opengl.cpp renderer_opengl/renderer_opengl.h @@ -88,6 +90,7 @@ add_library(video_core STATIC shader/decode/conversion.cpp shader/decode/memory.cpp shader/decode/texture.cpp + shader/decode/image.cpp shader/decode/float_set_predicate.cpp shader/decode/integer_set_predicate.cpp shader/decode/half_set_predicate.cpp @@ -109,6 +112,13 @@ add_library(video_core STATIC shader/track.cpp surface.cpp surface.h + texture_cache/surface_base.cpp + texture_cache/surface_base.h + texture_cache/surface_params.cpp + texture_cache/surface_params.h + texture_cache/surface_view.cpp + texture_cache/surface_view.h + texture_cache/texture_cache.h textures/astc.cpp textures/astc.h textures/convert.cpp @@ -116,8 +126,6 @@ add_library(video_core STATIC textures/decoders.cpp textures/decoders.h textures/texture.h - texture_cache.cpp - texture_cache.h video_core.cpp video_core.h ) diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp index 082a40cd9e..d44ad0cd8d 100644 --- a/src/video_core/engines/engine_upload.cpp +++ b/src/video_core/engines/engine_upload.cpp @@ -36,10 +36,10 @@ void State::ProcessData(const u32 data, const bool is_last_call) { } else { UNIMPLEMENTED_IF(regs.dest.z != 0); UNIMPLEMENTED_IF(regs.dest.depth != 1); - UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 1); - UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 1); + UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 0); + UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 0); const std::size_t dst_size = Tegra::Texture::CalculateSize( - true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 1); + true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 0); tmp_buffer.resize(dst_size); memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size); Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y, diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h index ef4f5839a0..462da419e2 100644 --- a/src/video_core/engines/engine_upload.h +++ b/src/video_core/engines/engine_upload.h @@ -39,15 +39,15 @@ struct Registers { } u32 BlockWidth() const { - return 1U << block_width.Value(); + return block_width.Value(); } u32 BlockHeight() const { - return 1U << block_height.Value(); + return block_height.Value(); } u32 BlockDepth() const { - return 1U << block_depth.Value(); + return block_depth.Value(); } } dest; }; diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 55966eef1c..0ee228e288 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -4,7 +4,6 @@ #include "common/assert.h" #include "common/logging/log.h" -#include "common/math_util.h" #include "video_core/engines/fermi_2d.h" #include "video_core/memory_manager.h" #include "video_core/rasterizer_interface.h" @@ -35,21 +34,31 @@ void Fermi2D::HandleSurfaceCopy() { static_cast(regs.operation)); // TODO(Subv): Only raw copies are implemented. - ASSERT(regs.operation == Regs::Operation::SrcCopy); + ASSERT(regs.operation == Operation::SrcCopy); const u32 src_blit_x1{static_cast(regs.blit_src_x >> 32)}; const u32 src_blit_y1{static_cast(regs.blit_src_y >> 32)}; - const u32 src_blit_x2{ - static_cast((regs.blit_src_x + (regs.blit_dst_width * regs.blit_du_dx)) >> 32)}; - const u32 src_blit_y2{ - static_cast((regs.blit_src_y + (regs.blit_dst_height * regs.blit_dv_dy)) >> 32)}; - + u32 src_blit_x2, src_blit_y2; + if (regs.blit_control.origin == Origin::Corner) { + src_blit_x2 = + static_cast((regs.blit_src_x + (regs.blit_du_dx * regs.blit_dst_width)) >> 32); + src_blit_y2 = + static_cast((regs.blit_src_y + (regs.blit_dv_dy * regs.blit_dst_height)) >> 32); + } else { + src_blit_x2 = static_cast((regs.blit_src_x >> 32) + regs.blit_dst_width); + src_blit_y2 = static_cast((regs.blit_src_y >> 32) + regs.blit_dst_height); + } const Common::Rectangle src_rect{src_blit_x1, src_blit_y1, src_blit_x2, src_blit_y2}; const Common::Rectangle dst_rect{regs.blit_dst_x, regs.blit_dst_y, regs.blit_dst_x + regs.blit_dst_width, regs.blit_dst_y + regs.blit_dst_height}; + Config copy_config; + copy_config.operation = regs.operation; + copy_config.filter = regs.blit_control.filter; + copy_config.src_rect = src_rect; + copy_config.dst_rect = dst_rect; - if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, src_rect, dst_rect)) { + if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst, copy_config)) { UNIMPLEMENTED(); } } diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h index 45f59a4d99..05421d1853 100644 --- a/src/video_core/engines/fermi_2d.h +++ b/src/video_core/engines/fermi_2d.h @@ -9,6 +9,7 @@ #include "common/bit_field.h" #include "common/common_funcs.h" #include "common/common_types.h" +#include "common/math_util.h" #include "video_core/gpu.h" namespace Tegra { @@ -38,6 +39,26 @@ public: /// Write the value to the register identified by method. void CallMethod(const GPU::MethodCall& method_call); + enum class Origin : u32 { + Center = 0, + Corner = 1, + }; + + enum class Filter : u32 { + PointSample = 0, // Nearest + Linear = 1, + }; + + enum class Operation : u32 { + SrcCopyAnd = 0, + ROPAnd = 1, + Blend = 2, + SrcCopy = 3, + ROP = 4, + SrcCopyPremult = 5, + BlendPremult = 6, + }; + struct Regs { static constexpr std::size_t NUM_REGS = 0x258; @@ -63,32 +84,19 @@ public: } u32 BlockWidth() const { - // The block width is stored in log2 format. - return 1 << block_width; + return block_width.Value(); } u32 BlockHeight() const { - // The block height is stored in log2 format. - return 1 << block_height; + return block_height.Value(); } u32 BlockDepth() const { - // The block depth is stored in log2 format. - return 1 << block_depth; + return block_depth.Value(); } }; static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size"); - enum class Operation : u32 { - SrcCopyAnd = 0, - ROPAnd = 1, - Blend = 2, - SrcCopy = 3, - ROP = 4, - SrcCopyPremult = 5, - BlendPremult = 6, - }; - union { struct { INSERT_PADDING_WORDS(0x80); @@ -105,7 +113,11 @@ public: INSERT_PADDING_WORDS(0x177); - u32 blit_control; + union { + u32 raw; + BitField<0, 1, Origin> origin; + BitField<4, 1, Filter> filter; + } blit_control; INSERT_PADDING_WORDS(0x8); @@ -124,6 +136,13 @@ public: }; } regs{}; + struct Config { + Operation operation; + Filter filter; + Common::Rectangle src_rect; + Common::Rectangle dst_rect; + }; + private: VideoCore::RasterizerInterface& rasterizer; MemoryManager& memory_manager; diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 08d5536966..8755b8af40 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -430,14 +430,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const { Texture::TICEntry tic_entry; memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); - ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear || - tic_entry.header_version == Texture::TICHeaderVersion::Pitch, - "TIC versions other than BlockLinear or Pitch are unimplemented"); - - const auto r_type = tic_entry.r_type.Value(); - const auto g_type = tic_entry.g_type.Value(); - const auto b_type = tic_entry.b_type.Value(); - const auto a_type = tic_entry.a_type.Value(); + const auto r_type{tic_entry.r_type.Value()}; + const auto g_type{tic_entry.g_type.Value()}; + const auto b_type{tic_entry.b_type.Value()}; + const auto a_type{tic_entry.a_type.Value()}; // TODO(Subv): Different data types for separate components are not supported DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 3a5dfef0c6..afb9578d0a 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -111,7 +111,7 @@ void MaxwellDMA::HandleCopy() { memory_manager.WriteBlock(dest, write_buffer.data(), dst_size); } else { - ASSERT(regs.dst_params.BlockDepth() == 1); + ASSERT(regs.dst_params.BlockDepth() == 0); const u32 src_bytes_per_pixel = regs.src_pitch / regs.x_count; diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index e5942f671c..17b015ca7d 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -59,11 +59,11 @@ public: }; u32 BlockHeight() const { - return 1 << block_height; + return block_height.Value(); } u32 BlockDepth() const { - return 1 << block_depth; + return block_depth.Value(); } }; diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index ffb3ec3e04..404d4f5aae 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -126,6 +127,15 @@ union Sampler { u64 value{}; }; +union Image { + Image() = default; + + constexpr explicit Image(u64 value) : value{value} {} + + BitField<36, 13, u64> index; + u64 value; +}; + } // namespace Tegra::Shader namespace std { @@ -344,6 +354,26 @@ enum class TextureMiscMode : u64 { PTP, }; +enum class SurfaceDataMode : u64 { + P = 0, + D_BA = 1, +}; + +enum class OutOfBoundsStore : u64 { + Ignore = 0, + Clamp = 1, + Trap = 2, +}; + +enum class ImageType : u64 { + Texture1D = 0, + TextureBuffer = 1, + Texture1DArray = 2, + Texture2D = 3, + Texture2DArray = 4, + Texture3D = 5, +}; + enum class IsberdMode : u64 { None = 0, Patch = 1, @@ -398,7 +428,7 @@ enum class LmemLoadCacheManagement : u64 { CV = 3, }; -enum class LmemStoreCacheManagement : u64 { +enum class StoreCacheManagement : u64 { Default = 0, CG = 1, CS = 2, @@ -811,7 +841,7 @@ union Instruction { } ld_l; union { - BitField<44, 2, LmemStoreCacheManagement> cache_management; + BitField<44, 2, StoreCacheManagement> cache_management; } st_l; union { @@ -1231,6 +1261,20 @@ union Instruction { } } texs; + union { + BitField<28, 1, u64> is_array; + BitField<29, 2, TextureType> texture_type; + BitField<35, 1, u64> aoffi; + BitField<49, 1, u64> nodep_flag; + BitField<50, 1, u64> ms; // Multisample? + BitField<54, 1, u64> cl; + BitField<55, 1, u64> process_mode; + + TextureProcessMode GetTextureProcessMode() const { + return process_mode == 0 ? TextureProcessMode::LZ : TextureProcessMode::LL; + } + } tld; + union { BitField<49, 1, u64> nodep_flag; BitField<53, 4, u64> texture_info; @@ -1280,6 +1324,35 @@ union Instruction { } } tlds; + union { + BitField<24, 2, StoreCacheManagement> cache_management; + BitField<33, 3, ImageType> image_type; + BitField<49, 2, OutOfBoundsStore> out_of_bounds_store; + BitField<51, 1, u64> is_immediate; + BitField<52, 1, SurfaceDataMode> mode; + + BitField<20, 3, StoreType> store_data_layout; + BitField<20, 4, u64> component_mask_selector; + + bool IsComponentEnabled(std::size_t component) const { + ASSERT(mode == SurfaceDataMode::P); + constexpr u8 R = 0b0001; + constexpr u8 G = 0b0010; + constexpr u8 B = 0b0100; + constexpr u8 A = 0b1000; + constexpr std::array mask = { + 0, (R), (G), (R | G), (B), (R | B), + (G | B), (R | G | B), (A), (R | A), (G | A), (R | G | A), + (B | A), (R | B | A), (G | B | A), (R | G | B | A)}; + return std::bitset<4>{mask.at(component_mask_selector)}.test(component); + } + + StoreType GetStoreDataLayout() const { + ASSERT(mode == SurfaceDataMode::D_BA); + return store_data_layout; + } + } sust; + union { BitField<20, 24, u64> target; BitField<5, 1, u64> constant_buffer; @@ -1371,6 +1444,7 @@ union Instruction { Attribute attribute; Sampler sampler; + Image image; u64 value; }; @@ -1408,11 +1482,13 @@ public: TXQ, // Texture Query TXQ_B, // Texture Query Bindless TEXS, // Texture Fetch with scalar/non-vec4 source/destinations + TLD, // Texture Load TLDS, // Texture Load with scalar/non-vec4 source/destinations TLD4, // Texture Load 4 TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations TMML_B, // Texture Mip Map Level TMML, // Texture Mip Map Level + SUST, // Surface Store EXIT, IPA, OUT_R, // Emit vertex/primitive @@ -1543,6 +1619,7 @@ public: Synch, Memory, Texture, + Image, FloatSet, FloatSetPredicate, IntegerSet, @@ -1682,11 +1759,13 @@ private: INST("1101111101001---", Id::TXQ, Type::Texture, "TXQ"), INST("1101111101010---", Id::TXQ_B, Type::Texture, "TXQ_B"), INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"), + INST("11011100--11----", Id::TLD, Type::Texture, "TLD"), INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"), INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"), INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"), INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), + INST("11101011001-----", Id::SUST, Type::Image, "SUST"), INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"), diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 5d8d126c18..3224531162 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -202,11 +202,12 @@ const u8* MemoryManager::GetPointer(GPUVAddr addr) const { } bool MemoryManager::IsBlockContinuous(const GPUVAddr start, const std::size_t size) const { - const GPUVAddr end = start + size; + const std::size_t inner_size = size - 1; + const GPUVAddr end = start + inner_size; const auto host_ptr_start = reinterpret_cast(GetPointer(start)); const auto host_ptr_end = reinterpret_cast(GetPointer(end)); const auto range = static_cast(host_ptr_end - host_ptr_start); - return range == size; + return range == inner_size; } void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, const std::size_t size) const { diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index d7b86df388..5ee4f8e8ec 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -10,6 +10,10 @@ #include "video_core/engines/fermi_2d.h" #include "video_core/gpu.h" +namespace Tegra { +class MemoryManager; +} + namespace VideoCore { enum class LoadCallbackStage { @@ -46,8 +50,7 @@ public: /// Attempt to use a faster method to perform a surface copy virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, const Tegra::Engines::Fermi2D::Regs::Surface& dst, - const Common::Rectangle& src_rect, - const Common::Rectangle& dst_rect) { + const Tegra::Engines::Fermi2D::Config& copy_config) { return false; } diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp b/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp new file mode 100644 index 0000000000..7c926bd48d --- /dev/null +++ b/src/video_core/renderer_opengl/gl_framebuffer_cache.cpp @@ -0,0 +1,75 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/cityhash.h" +#include "common/scope_exit.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_framebuffer_cache.h" +#include "video_core/renderer_opengl/gl_state.h" + +namespace OpenGL { + +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +FramebufferCacheOpenGL::FramebufferCacheOpenGL() = default; + +FramebufferCacheOpenGL::~FramebufferCacheOpenGL() = default; + +GLuint FramebufferCacheOpenGL::GetFramebuffer(const FramebufferCacheKey& key) { + const auto [entry, is_cache_miss] = cache.try_emplace(key); + auto& framebuffer{entry->second}; + if (is_cache_miss) { + framebuffer = CreateFramebuffer(key); + } + return framebuffer.handle; +} + +OGLFramebuffer FramebufferCacheOpenGL::CreateFramebuffer(const FramebufferCacheKey& key) { + OGLFramebuffer framebuffer; + framebuffer.Create(); + + // TODO(Rodrigo): Use DSA here after Nvidia fixes their framebuffer DSA bugs. + local_state.draw.draw_framebuffer = framebuffer.handle; + local_state.ApplyFramebufferState(); + + if (key.is_single_buffer) { + if (key.color_attachments[0] != GL_NONE && key.colors[0]) { + key.colors[0]->Attach(key.color_attachments[0], GL_DRAW_FRAMEBUFFER); + glDrawBuffer(key.color_attachments[0]); + } else { + glDrawBuffer(GL_NONE); + } + } else { + for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { + if (key.colors[index]) { + key.colors[index]->Attach(GL_COLOR_ATTACHMENT0 + static_cast(index), + GL_DRAW_FRAMEBUFFER); + } + } + glDrawBuffers(key.colors_count, key.color_attachments.data()); + } + + if (key.zeta) { + key.zeta->Attach(key.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT, + GL_DRAW_FRAMEBUFFER); + } + + return framebuffer; +} + +std::size_t FramebufferCacheKey::Hash() const { + static_assert(sizeof(*this) % sizeof(u64) == 0, "Unaligned struct"); + return static_cast( + Common::CityHash64(reinterpret_cast(this), sizeof(*this))); +} + +bool FramebufferCacheKey::operator==(const FramebufferCacheKey& rhs) const { + return std::tie(is_single_buffer, stencil_enable, colors_count, color_attachments, colors, + zeta) == std::tie(rhs.is_single_buffer, rhs.stencil_enable, rhs.colors_count, + rhs.color_attachments, rhs.colors, rhs.zeta); +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_framebuffer_cache.h b/src/video_core/renderer_opengl/gl_framebuffer_cache.h new file mode 100644 index 0000000000..a3a9963538 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_framebuffer_cache.h @@ -0,0 +1,68 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include + +#include "common/common_types.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_texture_cache.h" + +namespace OpenGL { + +struct alignas(sizeof(u64)) FramebufferCacheKey { + bool is_single_buffer = false; + bool stencil_enable = false; + u16 colors_count = 0; + + std::array color_attachments{}; + std::array colors; + View zeta; + + std::size_t Hash() const; + + bool operator==(const FramebufferCacheKey& rhs) const; + + bool operator!=(const FramebufferCacheKey& rhs) const { + return !operator==(rhs); + } +}; + +} // namespace OpenGL + +namespace std { + +template <> +struct hash { + std::size_t operator()(const OpenGL::FramebufferCacheKey& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace OpenGL { + +class FramebufferCacheOpenGL { +public: + FramebufferCacheOpenGL(); + ~FramebufferCacheOpenGL(); + + GLuint GetFramebuffer(const FramebufferCacheKey& key); + +private: + OGLFramebuffer CreateFramebuffer(const FramebufferCacheKey& key); + + OpenGLState local_state; + std::unordered_map cache; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index d774260678..f45a3c5efc 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -29,8 +29,10 @@ namespace OpenGL { using Maxwell = Tegra::Engines::Maxwell3D::Regs; -using PixelFormat = VideoCore::Surface::PixelFormat; -using SurfaceType = VideoCore::Surface::SurfaceType; + +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::SurfaceTarget; +using VideoCore::Surface::SurfaceType; MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Format Setup", MP_RGB(128, 128, 192)); MICROPROFILE_DEFINE(OpenGL_VB, "OpenGL", "Vertex Buffer Setup", MP_RGB(128, 128, 192)); @@ -78,29 +80,9 @@ struct DrawParameters { } }; -struct FramebufferCacheKey { - bool is_single_buffer = false; - bool stencil_enable = false; - - std::array color_attachments{}; - std::array colors{}; - u32 colors_count = 0; - - GLuint zeta = 0; - - auto Tie() const { - return std::tie(is_single_buffer, stencil_enable, color_attachments, colors, colors_count, - zeta); - } - - bool operator<(const FramebufferCacheKey& rhs) const { - return Tie() < rhs.Tie(); - } -}; - RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWindow& emu_window, ScreenInfo& info) - : res_cache{*this}, shader_cache{*this, system, emu_window, device}, + : texture_cache{system, *this, device}, shader_cache{*this, system, emu_window, device}, global_cache{*this}, system{system}, screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) { OpenGLState::ApplyDefaultState(); @@ -121,11 +103,6 @@ void RasterizerOpenGL::CheckExtensions() { Render_OpenGL, "Anisotropic filter is not supported! This can cause graphical issues in some games."); } - if (!GLAD_GL_ARB_buffer_storage) { - LOG_WARNING( - Render_OpenGL, - "Buffer storage control is not supported! This can cause performance degradation."); - } } GLuint RasterizerOpenGL::SetupVertexFormat() { @@ -302,8 +279,14 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { static_cast(sizeof(ubo))); Shader shader{shader_cache.GetStageProgram(program)}; - const auto [program_handle, next_bindings] = - shader->GetProgramHandle(primitive_mode, base_bindings); + + const auto stage_enum{static_cast(stage)}; + SetupDrawConstBuffers(stage_enum, shader); + SetupGlobalRegions(stage_enum, shader); + const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)}; + + const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage}; + const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant); switch (program) { case Maxwell::ShaderProgram::VertexA: @@ -321,11 +304,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { shader_config.enable.Value(), shader_config.offset); } - const auto stage_enum = static_cast(stage); - SetupDrawConstBuffers(stage_enum, shader); - SetupGlobalRegions(stage_enum, shader); - SetupTextures(stage_enum, shader, base_bindings); - // Workaround for Intel drivers. // When a clip distance is enabled but not set in the shader it crops parts of the screen // (sometimes it's half the screen, sometimes three quarters). To avoid this, enable the @@ -351,44 +329,6 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { gpu.dirty_flags.shaders = false; } -void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, - OpenGLState& current_state) { - const auto [entry, is_cache_miss] = framebuffer_cache.try_emplace(fbkey); - auto& framebuffer = entry->second; - - if (is_cache_miss) - framebuffer.Create(); - - current_state.draw.draw_framebuffer = framebuffer.handle; - current_state.ApplyFramebufferState(); - - if (!is_cache_miss) - return; - - if (fbkey.is_single_buffer) { - if (fbkey.color_attachments[0] != GL_NONE) { - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, fbkey.color_attachments[0], fbkey.colors[0], - 0); - } - glDrawBuffer(fbkey.color_attachments[0]); - } else { - for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { - if (fbkey.colors[index]) { - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, - GL_COLOR_ATTACHMENT0 + static_cast(index), - fbkey.colors[index], 0); - } - } - glDrawBuffers(fbkey.colors_count, fbkey.color_attachments.data()); - } - - if (fbkey.zeta) { - GLenum zeta_attachment = - fbkey.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT; - glFramebufferTexture(GL_DRAW_FRAMEBUFFER, zeta_attachment, fbkey.zeta, 0); - } -} - std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const { const auto& regs = system.GPU().Maxwell3D().regs; @@ -478,9 +418,13 @@ std::pair RasterizerOpenGL::ConfigureFramebuffers( } current_framebuffer_config_state = fb_config_state; - Surface depth_surface; + texture_cache.GuardRenderTargets(true); + + View depth_surface{}; if (using_depth_fb) { - depth_surface = res_cache.GetDepthBufferSurface(preserve_contents); + depth_surface = texture_cache.GetDepthBufferSurface(preserve_contents); + } else { + texture_cache.SetEmptyDepthBuffer(); } UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0); @@ -493,13 +437,13 @@ std::pair RasterizerOpenGL::ConfigureFramebuffers( if (using_color_fb) { if (single_color_target) { // Used when just a single color attachment is enabled, e.g. for clearing a color buffer - Surface color_surface = - res_cache.GetColorBufferSurface(*single_color_target, preserve_contents); + View color_surface{ + texture_cache.GetColorBufferSurface(*single_color_target, preserve_contents)}; if (color_surface) { // Assume that a surface will be written to if it is used as a framebuffer, even if // the shader doesn't actually write to it. - color_surface->MarkAsModified(true, res_cache); + texture_cache.MarkColorBufferInUse(*single_color_target); // Workaround for and issue in nvidia drivers // https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/ state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion; @@ -508,16 +452,21 @@ std::pair RasterizerOpenGL::ConfigureFramebuffers( fbkey.is_single_buffer = true; fbkey.color_attachments[0] = GL_COLOR_ATTACHMENT0 + static_cast(*single_color_target); - fbkey.colors[0] = color_surface != nullptr ? color_surface->Texture().handle : 0; + fbkey.colors[0] = color_surface; + for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { + if (index != *single_color_target) { + texture_cache.SetEmptyColorBuffer(index); + } + } } else { // Multiple color attachments are enabled for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { - Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents); + View color_surface{texture_cache.GetColorBufferSurface(index, preserve_contents)}; if (color_surface) { // Assume that a surface will be written to if it is used as a framebuffer, even // if the shader doesn't actually write to it. - color_surface->MarkAsModified(true, res_cache); + texture_cache.MarkColorBufferInUse(index); // Enable sRGB only for supported formats // Workaround for and issue in nvidia drivers // https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/ @@ -527,8 +476,7 @@ std::pair RasterizerOpenGL::ConfigureFramebuffers( fbkey.color_attachments[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); - fbkey.colors[index] = - color_surface != nullptr ? color_surface->Texture().handle : 0; + fbkey.colors[index] = color_surface; } fbkey.is_single_buffer = false; fbkey.colors_count = regs.rt_control.count; @@ -541,14 +489,16 @@ std::pair RasterizerOpenGL::ConfigureFramebuffers( if (depth_surface) { // Assume that a surface will be written to if it is used as a framebuffer, even if // the shader doesn't actually write to it. - depth_surface->MarkAsModified(true, res_cache); + texture_cache.MarkDepthBufferInUse(); - fbkey.zeta = depth_surface->Texture().handle; + fbkey.zeta = depth_surface; fbkey.stencil_enable = regs.stencil_enable && depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil; } - SetupCachedFramebuffer(fbkey, current_state); + texture_cache.GuardRenderTargets(false); + + current_state.draw.draw_framebuffer = framebuffer_cache.GetFramebuffer(fbkey); SyncViewport(current_state); return current_depth_stencil_usage = {static_cast(depth_surface), fbkey.stencil_enable}; @@ -630,6 +580,7 @@ void RasterizerOpenGL::Clear() { clear_state.ApplyDepth(); clear_state.ApplyStencilTest(); clear_state.ApplyViewport(); + clear_state.ApplyFramebufferState(); if (use_color) { glClearBufferfv(GL_COLOR, regs.clear_buffers.RT, regs.clear_color); @@ -652,7 +603,6 @@ void RasterizerOpenGL::DrawArrays() { auto& gpu = system.GPU().Maxwell3D(); const auto& regs = gpu.regs; - ConfigureFramebuffers(state); SyncColorMask(); SyncFragmentColorClampState(); SyncMultiSampleState(); @@ -697,16 +647,22 @@ void RasterizerOpenGL::DrawArrays() { SetupVertexBuffer(vao); DrawParameters params = SetupDraw(); + texture_cache.GuardSamplers(true); SetupShaders(params.primitive_mode); + texture_cache.GuardSamplers(false); + + ConfigureFramebuffers(state); buffer_cache.Unmap(); shader_program_manager->ApplyTo(state); state.Apply(); - res_cache.SignalPreDrawCall(); + if (texture_cache.TextureBarrier()) { + glTextureBarrier(); + } + params.DispatchDraw(); - res_cache.SignalPostDrawCall(); accelerate_draw = AccelDraw::Disabled; } @@ -718,7 +674,7 @@ void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) { if (!addr || !size) { return; } - res_cache.FlushRegion(addr, size); + texture_cache.FlushRegion(addr, size); global_cache.FlushRegion(addr, size); } @@ -727,23 +683,24 @@ void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) { if (!addr || !size) { return; } - res_cache.InvalidateRegion(addr, size); + texture_cache.InvalidateRegion(addr, size); shader_cache.InvalidateRegion(addr, size); global_cache.InvalidateRegion(addr, size); buffer_cache.InvalidateRegion(addr, size); } void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) { - FlushRegion(addr, size); + if (Settings::values.use_accurate_gpu_emulation) { + FlushRegion(addr, size); + } InvalidateRegion(addr, size); } bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, const Tegra::Engines::Fermi2D::Regs::Surface& dst, - const Common::Rectangle& src_rect, - const Common::Rectangle& dst_rect) { + const Tegra::Engines::Fermi2D::Config& copy_config) { MICROPROFILE_SCOPE(OpenGL_Blits); - res_cache.FermiCopySurface(src, dst, src_rect, dst_rect); + texture_cache.DoFermiCopy(src, dst, copy_config); return true; } @@ -755,7 +712,8 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, MICROPROFILE_SCOPE(OpenGL_CacheManagement); - const auto& surface{res_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))}; + const auto surface{ + texture_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))}; if (!surface) { return {}; } @@ -771,7 +729,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config, LOG_WARNING(Render_OpenGL, "Framebuffer pixel_format is different"); } - screen_info.display_texture = surface->Texture().handle; + screen_info.display_texture = surface->GetTexture(); return true; } @@ -837,8 +795,8 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade } } -void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, - BaseBindings base_bindings) { +TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, + BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); @@ -847,6 +805,8 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units), "Exceeded the number of active textures."); + TextureBufferUsage texture_buffer_usage{0}; + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry = entries[bindpoint]; Tegra::Texture::FullTextureInfo texture; @@ -860,18 +820,26 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s } const u32 current_bindpoint = base_bindings.sampler + bindpoint; - state.texture_units[current_bindpoint].sampler = sampler_cache.GetSampler(texture.tsc); + auto& unit{state.texture_units[current_bindpoint]}; + unit.sampler = sampler_cache.GetSampler(texture.tsc); - if (Surface surface = res_cache.GetTextureSurface(texture, entry); surface) { - state.texture_units[current_bindpoint].texture = - surface->Texture(entry.IsArray()).handle; - surface->UpdateSwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, + if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) { + if (view->GetSurfaceParams().IsBuffer()) { + // Record that this texture is a texture buffer. + texture_buffer_usage.set(bindpoint); + } else { + // Apply swizzle to textures that are not buffers. + view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, texture.tic.w_source); + } + state.texture_units[current_bindpoint].texture = view->GetTexture(); } else { // Can occur when texture addr is null or its memory is unmapped/invalid - state.texture_units[current_bindpoint].texture = 0; + unit.texture = 0; } } + + return texture_buffer_usage; } void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index f7671ff5d1..bf67e3a70d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -23,14 +23,15 @@ #include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_buffer_cache.h" #include "video_core/renderer_opengl/gl_device.h" +#include "video_core/renderer_opengl/gl_framebuffer_cache.h" #include "video_core/renderer_opengl/gl_global_cache.h" -#include "video_core/renderer_opengl/gl_rasterizer_cache.h" #include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_sampler_cache.h" #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_decompiler.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_texture_cache.h" #include "video_core/renderer_opengl/utils.h" namespace Core { @@ -41,11 +42,14 @@ namespace Core::Frontend { class EmuWindow; } +namespace Tegra { +class MemoryManager; +} + namespace OpenGL { struct ScreenInfo; struct DrawParameters; -struct FramebufferCacheKey; class RasterizerOpenGL : public VideoCore::RasterizerInterface { public: @@ -61,8 +65,7 @@ public: void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override; bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, const Tegra::Engines::Fermi2D::Regs::Surface& dst, - const Common::Rectangle& src_rect, - const Common::Rectangle& dst_rect) override; + const Tegra::Engines::Fermi2D::Config& copy_config) override; bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr, u32 pixel_stride) override; bool AccelerateDrawBatch(bool is_indexed) override; @@ -95,6 +98,8 @@ private: /** * Configures the color and depth framebuffer states. + * @param must_reconfigure If true, tells the framebuffer to skip the cache and reconfigure + * again. Used by the texture cache to solve texception conflicts * @param use_color_fb If true, configure color framebuffers. * @param using_depth_fb If true, configure the depth/stencil framebuffer. * @param preserve_contents If true, tries to preserve data from a previously used framebuffer. @@ -118,9 +123,10 @@ private: void SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader); - /// Configures the current textures to use for the draw command. - void SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, - BaseBindings base_bindings); + /// Configures the current textures to use for the draw command. Returns shaders texture buffer + /// usage. + TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader, BaseBindings base_bindings); /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); @@ -181,10 +187,11 @@ private: const Device device; OpenGLState state; - RasterizerCacheOpenGL res_cache; + TextureCacheOpenGL texture_cache; ShaderCacheOpenGL shader_cache; GlobalRegionCacheOpenGL global_cache; SamplerCacheOpenGL sampler_cache; + FramebufferCacheOpenGL framebuffer_cache; Core::System& system; ScreenInfo& screen_info; @@ -195,7 +202,6 @@ private: OGLVertexArray> vertex_array_cache; - std::map framebuffer_cache; FramebufferConfigState current_framebuffer_config_state; std::pair current_depth_stencil_usage{}; @@ -218,8 +224,6 @@ private: void SetupShaders(GLenum primitive_mode); - void SetupCachedFramebuffer(const FramebufferCacheKey& fbkey, OpenGLState& current_state); - enum class AccelDraw { Disabled, Arrays, Indexed }; AccelDraw accelerate_draw = AccelDraw::Disabled; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp deleted file mode 100644 index a7681902ef..0000000000 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ /dev/null @@ -1,1362 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/logging/log.h" -#include "common/microprofile.h" -#include "common/scope_exit.h" -#include "core/core.h" -#include "core/hle/kernel/process.h" -#include "core/settings.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/memory_manager.h" -#include "video_core/morton.h" -#include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_rasterizer_cache.h" -#include "video_core/renderer_opengl/utils.h" -#include "video_core/surface.h" -#include "video_core/textures/convert.h" -#include "video_core/textures/decoders.h" - -namespace OpenGL { - -using VideoCore::MortonSwizzle; -using VideoCore::MortonSwizzleMode; -using VideoCore::Surface::ComponentTypeFromDepthFormat; -using VideoCore::Surface::ComponentTypeFromRenderTarget; -using VideoCore::Surface::ComponentTypeFromTexture; -using VideoCore::Surface::PixelFormatFromDepthFormat; -using VideoCore::Surface::PixelFormatFromRenderTargetFormat; -using VideoCore::Surface::PixelFormatFromTextureFormat; -using VideoCore::Surface::SurfaceTargetFromTextureType; - -struct FormatTuple { - GLint internal_format; - GLenum format; - GLenum type; - ComponentType component_type; - bool compressed; -}; - -static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) { - glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, max_mip_level - 1); - if (max_mip_level == 1) { - glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0); - } -} - -void SurfaceParams::InitCacheParameters(GPUVAddr gpu_addr_) { - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - - gpu_addr = gpu_addr_; - host_ptr = memory_manager.GetPointer(gpu_addr_); - size_in_bytes = SizeInBytesRaw(); - - if (IsPixelFormatASTC(pixel_format)) { - // ASTC is uncompressed in software, in emulated as RGBA8 - size_in_bytes_gl = width * height * depth * 4; - } else { - size_in_bytes_gl = SizeInBytesGL(); - } -} - -std::size_t SurfaceParams::InnerMipmapMemorySize(u32 mip_level, bool force_gl, bool layer_only, - bool uncompressed) const { - const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; - const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; - const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)}; - u32 m_depth = (layer_only ? 1U : depth); - u32 m_width = MipWidth(mip_level); - u32 m_height = MipHeight(mip_level); - m_width = uncompressed ? m_width : std::max(1U, (m_width + tile_x - 1) / tile_x); - m_height = uncompressed ? m_height : std::max(1U, (m_height + tile_y - 1) / tile_y); - m_depth = std::max(1U, m_depth >> mip_level); - u32 m_block_height = MipBlockHeight(mip_level); - u32 m_block_depth = MipBlockDepth(mip_level); - return Tegra::Texture::CalculateSize(force_gl ? false : is_tiled, bytes_per_pixel, m_width, - m_height, m_depth, m_block_height, m_block_depth); -} - -std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, - bool uncompressed) const { - std::size_t block_size_bytes = Tegra::Texture::GetGOBSize() * block_height * block_depth; - std::size_t size = 0; - for (u32 i = 0; i < max_mip_level; i++) { - size += InnerMipmapMemorySize(i, force_gl, layer_only, uncompressed); - } - if (!force_gl && is_tiled) { - size = Common::AlignUp(size, block_size_bytes); - } - return size; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForTexture( - const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) { - SurfaceParams params{}; - params.is_tiled = config.tic.IsTiled(); - params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, - params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, - params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, - params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; - params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); - params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), - params.srgb_conversion); - - if (config.tsc.depth_compare_enabled) { - // Some titles create a 'R16U' (normalized 16-bit) texture with depth_compare enabled, - // then attempt to sample from it via a shadow sampler. Convert format to Z16 (which also - // causes GetFormatType to properly return 'Depth' below). - if (GetFormatType(params.pixel_format) == SurfaceType::ColorTexture) { - switch (params.pixel_format) { - case PixelFormat::R16S: - case PixelFormat::R16U: - case PixelFormat::R16F: - params.pixel_format = PixelFormat::Z16; - break; - case PixelFormat::R32F: - params.pixel_format = PixelFormat::Z32F; - break; - default: - LOG_WARNING(HW_GPU, "Color texture format being used with depth compare: {}", - static_cast(params.pixel_format)); - break; - } - } - } - - params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); - params.type = GetFormatType(params.pixel_format); - UNIMPLEMENTED_IF(params.type == SurfaceType::ColorTexture && config.tsc.depth_compare_enabled); - - params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); - params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); - if (!params.is_tiled) { - params.pitch = config.tic.Pitch(); - } - params.unaligned_height = config.tic.Height(); - params.target = SurfaceTargetFromTextureType(config.tic.texture_type); - params.identity = SurfaceClass::Uploaded; - - switch (params.target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - params.depth = 1; - break; - case SurfaceTarget::TextureCubemap: - params.depth = config.tic.Depth() * 6; - break; - case SurfaceTarget::Texture3D: - params.depth = config.tic.Depth(); - break; - case SurfaceTarget::Texture2DArray: - params.depth = config.tic.Depth(); - if (!entry.IsArray()) { - // TODO(bunnei): We have seen games re-use a Texture2D as Texture2DArray with depth of - // one, but sample the texture in the shader as if it were not an array texture. This - // probably is valid on hardware, but we still need to write a test to confirm this. In - // emulation, the workaround here is to continue to treat this as a Texture2D. An - // example game that does this is Super Mario Odyssey (in Cloud Kingdom). - ASSERT(params.depth == 1); - params.target = SurfaceTarget::Texture2D; - } - break; - case SurfaceTarget::TextureCubeArray: - params.depth = config.tic.Depth() * 6; - if (!entry.IsArray()) { - ASSERT(params.depth == 6); - params.target = SurfaceTarget::TextureCubemap; - } - break; - default: - LOG_CRITICAL(HW_GPU, "Unknown depth for target={}", static_cast(params.target)); - UNREACHABLE(); - params.depth = 1; - break; - } - - params.is_layered = SurfaceTargetIsLayered(params.target); - params.is_array = SurfaceTargetIsArray(params.target); - params.max_mip_level = config.tic.max_mip_level + 1; - params.rt = {}; - - params.InitCacheParameters(config.tic.Address()); - - return params; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) { - const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]}; - SurfaceParams params{}; - - params.is_tiled = - config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << config.memory_layout.block_width; - params.block_height = 1 << config.memory_layout.block_height; - params.block_depth = 1 << config.memory_layout.block_depth; - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || - config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - if (params.is_tiled) { - params.width = config.width; - } else { - params.pitch = config.width; - const u32 bpp = params.GetFormatBpp() / 8; - params.width = params.pitch / bpp; - } - params.height = config.height; - params.unaligned_height = config.height; - params.target = SurfaceTarget::Texture2D; - params.identity = SurfaceClass::RenderTarget; - params.depth = 1; - params.max_mip_level = 1; - params.is_layered = false; - - // Render target specific parameters, not used for caching - params.rt.index = static_cast(index); - params.rt.array_mode = config.array_mode; - params.rt.layer_stride = config.layer_stride; - params.rt.volume = config.volume; - params.rt.base_layer = config.base_layer; - - params.InitCacheParameters(config.Address()); - - return params; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer( - u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { - SurfaceParams params{}; - - params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << std::min(block_width, 5U); - params.block_height = 1 << std::min(block_height, 5U); - params.block_depth = 1 << std::min(block_depth, 5U); - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromDepthFormat(format); - params.component_type = ComponentTypeFromDepthFormat(format); - params.type = GetFormatType(params.pixel_format); - params.srgb_conversion = false; - params.width = zeta_width; - params.height = zeta_height; - params.unaligned_height = zeta_height; - params.target = SurfaceTarget::Texture2D; - params.identity = SurfaceClass::DepthBuffer; - params.depth = 1; - params.max_mip_level = 1; - params.is_layered = false; - params.rt = {}; - - params.InitCacheParameters(zeta_address); - - return params; -} - -/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - SurfaceParams params{}; - - params.is_tiled = !config.linear; - params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, - params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, - params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || - config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - params.width = config.width; - params.pitch = config.pitch; - params.height = config.height; - params.unaligned_height = config.height; - params.target = SurfaceTarget::Texture2D; - params.identity = SurfaceClass::Copy; - params.depth = 1; - params.max_mip_level = 1; - params.rt = {}; - - params.InitCacheParameters(config.Address()); - - return params; -} - -static constexpr std::array tex_format_tuples = {{ - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U - {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S - {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI - {GL_RGB8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U - {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, - false}, // A2B10G10R10U - {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U - {GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8U - {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI - {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F - {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U - {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI - {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float, - false}, // R11FG11FB10F - {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI - {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT1 - {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT23 - {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT45 - {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1 - {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXN2UNORM - {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM - {GL_COMPRESSED_RGBA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // BC7U - {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, - true}, // BC6H_UF16 - {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, - true}, // BC6H_SF16 - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 - {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 - {GL_RGBA32F, GL_RGBA, GL_FLOAT, ComponentType::Float, false}, // RGBA32F - {GL_RG32F, GL_RG, GL_FLOAT, ComponentType::Float, false}, // RG32F - {GL_R32F, GL_RED, GL_FLOAT, ComponentType::Float, false}, // R32F - {GL_R16F, GL_RED, GL_HALF_FLOAT, ComponentType::Float, false}, // R16F - {GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // R16U - {GL_R16_SNORM, GL_RED, GL_SHORT, ComponentType::SNorm, false}, // R16S - {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // R16UI - {GL_R16I, GL_RED_INTEGER, GL_SHORT, ComponentType::SInt, false}, // R16I - {GL_RG16, GL_RG, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RG16 - {GL_RG16F, GL_RG, GL_HALF_FLOAT, ComponentType::Float, false}, // RG16F - {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RG16UI - {GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I - {GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S - {GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, - false}, // RGBA8_SRGB - {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U - {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S - {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI - {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8 - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5 - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4 - {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 - // Compressed sRGB formats - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT1_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT23_SRGB - {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // DXT45_SRGB - {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, - true}, // BC7U_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5 - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB - {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8 - {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8_SRGB - - // Depth formats - {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F - {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm, - false}, // Z16 - - // DepthStencil formats - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, - false}, // Z24S8 - {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, - false}, // S8Z24 - {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, - ComponentType::Float, false}, // Z32FS8 -}}; - -static GLenum SurfaceTargetToGL(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - return GL_TEXTURE_1D; - case SurfaceTarget::Texture2D: - return GL_TEXTURE_2D; - case SurfaceTarget::Texture3D: - return GL_TEXTURE_3D; - case SurfaceTarget::Texture1DArray: - return GL_TEXTURE_1D_ARRAY; - case SurfaceTarget::Texture2DArray: - return GL_TEXTURE_2D_ARRAY; - case SurfaceTarget::TextureCubemap: - return GL_TEXTURE_CUBE_MAP; - case SurfaceTarget::TextureCubeArray: - return GL_TEXTURE_CUBE_MAP_ARRAY; - } - LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast(target)); - UNREACHABLE(); - return {}; -} - -static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { - ASSERT(static_cast(pixel_format) < tex_format_tuples.size()); - auto& format = tex_format_tuples[static_cast(pixel_format)]; - ASSERT(component_type == format.component_type); - - return format; -} - -/// Returns the discrepant array target -constexpr GLenum GetArrayDiscrepantTarget(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - return GL_TEXTURE_1D_ARRAY; - case SurfaceTarget::Texture2D: - return GL_TEXTURE_2D_ARRAY; - case SurfaceTarget::Texture3D: - return GL_NONE; - case SurfaceTarget::Texture1DArray: - return GL_TEXTURE_1D; - case SurfaceTarget::Texture2DArray: - return GL_TEXTURE_2D; - case SurfaceTarget::TextureCubemap: - return GL_TEXTURE_CUBE_MAP_ARRAY; - case SurfaceTarget::TextureCubeArray: - return GL_TEXTURE_CUBE_MAP; - } - return GL_NONE; -} - -Common::Rectangle SurfaceParams::GetRect(u32 mip_level) const { - u32 actual_height{std::max(1U, unaligned_height >> mip_level)}; - if (IsPixelFormatASTC(pixel_format)) { - // ASTC formats must stop at the ATSC block size boundary - actual_height = Common::AlignDown(actual_height, GetASTCBlockSize(pixel_format).second); - } - return {0, actual_height, MipWidth(mip_level), 0}; -} - -void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params, - std::vector& gl_buffer, u32 mip_level) { - u32 depth = params.MipDepth(mip_level); - if (params.target == SurfaceTarget::Texture2D) { - // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented. - depth = 1U; - } - if (params.is_layered) { - u64 offset = params.GetMipmapLevelOffset(mip_level); - u64 offset_gl = 0; - const u64 layer_size = params.LayerMemorySize(); - const u64 gl_size = params.LayerSizeGL(mip_level); - for (u32 i = 0; i < params.depth; i++) { - MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), - params.MipBlockHeight(mip_level), params.MipHeight(mip_level), - params.MipBlockDepth(mip_level), 1, params.tile_width_spacing, - gl_buffer.data() + offset_gl, params.host_ptr + offset); - offset += layer_size; - offset_gl += gl_size; - } - } else { - const u64 offset = params.GetMipmapLevelOffset(mip_level); - MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level), - params.MipBlockHeight(mip_level), params.MipHeight(mip_level), - params.MipBlockDepth(mip_level), depth, params.tile_width_spacing, - gl_buffer.data(), params.host_ptr + offset); - } -} - -void RasterizerCacheOpenGL::FastCopySurface(const Surface& src_surface, - const Surface& dst_surface) { - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - const u32 width{std::min(src_params.width, dst_params.width)}; - const u32 height{std::min(src_params.height, dst_params.height)}; - - glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0, - 0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0, - 0, 0, width, height, 1); - - dst_surface->MarkAsModified(true, *this); -} - -MICROPROFILE_DEFINE(OpenGL_CopySurface, "OpenGL", "CopySurface", MP_RGB(128, 192, 64)); -void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface, - const GLuint copy_pbo_handle, const GLenum src_attachment, - const GLenum dst_attachment, - const std::size_t cubemap_face) { - MICROPROFILE_SCOPE(OpenGL_CopySurface); - ASSERT_MSG(dst_attachment == 0, "Unimplemented"); - - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - const auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type); - const auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type); - - const std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes); - - glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); - glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_COPY); - if (source_format.compressed) { - glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment, - static_cast(src_params.size_in_bytes), nullptr); - } else { - glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format, - source_format.type, static_cast(src_params.size_in_bytes), - nullptr); - } - // If the new texture is bigger than the previous one, we need to fill in the rest with data - // from the CPU. - if (src_params.size_in_bytes < dst_params.size_in_bytes) { - // Upload the rest of the memory. - if (dst_params.is_tiled) { - // TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest - // of the data in this case. Games like Super Mario Odyssey seem to hit this case - // when drawing, it re-uses the memory of a previous texture as a bigger framebuffer - // but it doesn't clear it beforehand, the texture is already full of zeros. - LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during " - "reinterpretation but the texture is tiled."); - } - const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes; - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size, - memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes)); - } - - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - - const GLsizei width{static_cast( - std::min(src_params.GetRect().GetWidth(), dst_params.GetRect().GetWidth()))}; - const GLsizei height{static_cast( - std::min(src_params.GetRect().GetHeight(), dst_params.GetRect().GetHeight()))}; - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo_handle); - if (dest_format.compressed) { - LOG_CRITICAL(HW_GPU, "Compressed copy is unimplemented!"); - UNREACHABLE(); - } else { - switch (dst_params.target) { - case SurfaceTarget::Texture1D: - glTextureSubImage1D(dst_surface->Texture().handle, 0, 0, width, dest_format.format, - dest_format.type, nullptr); - break; - case SurfaceTarget::Texture2D: - glTextureSubImage2D(dst_surface->Texture().handle, 0, 0, 0, width, height, - dest_format.format, dest_format.type, nullptr); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, 0, width, height, - static_cast(dst_params.depth), dest_format.format, - dest_format.type, nullptr); - break; - case SurfaceTarget::TextureCubemap: - glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, - static_cast(cubemap_face), width, height, 1, - dest_format.format, dest_format.type, nullptr); - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast(dst_params.target)); - UNREACHABLE(); - } - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - } - - dst_surface->MarkAsModified(true, *this); -} - -CachedSurface::CachedSurface(const SurfaceParams& params) - : RasterizerCacheObject{params.host_ptr}, params{params}, - gl_target{SurfaceTargetToGL(params.target)}, cached_size_in_bytes{params.size_in_bytes} { - - const auto optional_cpu_addr{ - Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(params.gpu_addr)}; - ASSERT_MSG(optional_cpu_addr, "optional_cpu_addr is invalid"); - cpu_addr = *optional_cpu_addr; - - texture.Create(gl_target); - - // TODO(Rodrigo): Using params.GetRect() returns a different size than using its Mip*(0) - // alternatives. This signals a bug on those functions. - const auto width = static_cast(params.MipWidth(0)); - const auto height = static_cast(params.MipHeight(0)); - memory_size = params.MemorySize(); - reinterpreted = false; - - const auto& format_tuple = GetFormatTuple(params.pixel_format, params.component_type); - gl_internal_format = format_tuple.internal_format; - - switch (params.target) { - case SurfaceTarget::Texture1D: - glTextureStorage1D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width); - break; - case SurfaceTarget::Texture2D: - case SurfaceTarget::TextureCubemap: - glTextureStorage2D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width, height); - break; - case SurfaceTarget::Texture3D: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureStorage3D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width, height, params.depth); - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast(params.target)); - UNREACHABLE(); - glTextureStorage2D(texture.handle, params.max_mip_level, format_tuple.internal_format, - width, height); - } - - ApplyTextureDefaults(texture.handle, params.max_mip_level); - - OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString()); -} - -MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64)); -void CachedSurface::LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) { - MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); - auto& gl_buffer = res_cache_tmp_mem.gl_buffer; - if (gl_buffer.size() < params.max_mip_level) - gl_buffer.resize(params.max_mip_level); - for (u32 i = 0; i < params.max_mip_level; i++) - gl_buffer[i].resize(params.GetMipmapSizeGL(i)); - if (params.is_tiled) { - ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", - params.block_width, static_cast(params.target)); - for (u32 i = 0; i < params.max_mip_level; i++) - SwizzleFunc(MortonSwizzleMode::MortonToLinear, params, gl_buffer[i], i); - } else { - const u32 bpp = params.GetFormatBpp() / 8; - const u32 copy_size = (params.width * bpp + GetDefaultBlockWidth(params.pixel_format) - 1) / - GetDefaultBlockWidth(params.pixel_format); - if (params.pitch == copy_size) { - std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl); - } else { - const u32 height = (params.height + GetDefaultBlockHeight(params.pixel_format) - 1) / - GetDefaultBlockHeight(params.pixel_format); - const u8* start{params.host_ptr}; - u8* write_to = gl_buffer[0].data(); - for (u32 h = height; h > 0; h--) { - std::memcpy(write_to, start, copy_size); - start += params.pitch; - write_to += copy_size; - } - } - } - for (u32 i = 0; i < params.max_mip_level; i++) { - const u32 width = params.MipWidth(i); - const u32 height = params.MipHeight(i); - const u32 depth = params.MipDepth(i); - if (VideoCore::Surface::IsPixelFormatASTC(params.pixel_format)) { - // Reserve size for RGBA8 conversion - constexpr std::size_t rgba_bpp = 4; - gl_buffer[i].resize(std::max(gl_buffer[i].size(), width * height * depth * rgba_bpp)); - } - Tegra::Texture::ConvertFromGuestToHost(gl_buffer[i].data(), params.pixel_format, width, - height, depth, true, true); - } -} - -MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); -void CachedSurface::FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem) { - MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); - - ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented"); - - auto& gl_buffer = res_cache_tmp_mem.gl_buffer; - // OpenGL temporary buffer needs to be big enough to store raw texture size - gl_buffer[0].resize(GetSizeInBytes()); - - const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); - const u32 align = std::clamp(params.RowAlign(0), 1U, 8U); - glPixelStorei(GL_PACK_ALIGNMENT, align); - glPixelStorei(GL_PACK_ROW_LENGTH, static_cast(params.width)); - ASSERT(!tuple.compressed); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, - static_cast(gl_buffer[0].size()), gl_buffer[0].data()); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - Tegra::Texture::ConvertFromHostToGuest(gl_buffer[0].data(), params.pixel_format, params.width, - params.height, params.depth, true, true); - if (params.is_tiled) { - ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", - params.block_width, static_cast(params.target)); - - SwizzleFunc(MortonSwizzleMode::LinearToMorton, params, gl_buffer[0], 0); - } else { - const u32 bpp = params.GetFormatBpp() / 8; - const u32 copy_size = params.width * bpp; - if (params.pitch == copy_size) { - std::memcpy(params.host_ptr, gl_buffer[0].data(), GetSizeInBytes()); - } else { - u8* start{params.host_ptr}; - const u8* read_to = gl_buffer[0].data(); - for (u32 h = params.height; h > 0; h--) { - std::memcpy(start, read_to, copy_size); - start += params.pitch; - read_to += copy_size; - } - } - } -} - -void CachedSurface::UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map, - GLuint read_fb_handle, GLuint draw_fb_handle) { - const auto& rect{params.GetRect(mip_map)}; - - auto& gl_buffer = res_cache_tmp_mem.gl_buffer; - - // Load data from memory to the surface - const auto x0 = static_cast(rect.left); - const auto y0 = static_cast(rect.bottom); - auto buffer_offset = - static_cast(static_cast(y0) * params.MipWidth(mip_map) + - static_cast(x0)) * - GetBytesPerPixel(params.pixel_format); - - const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); - - const u32 align = std::clamp(params.RowAlign(mip_map), 1U, 8U); - glPixelStorei(GL_UNPACK_ALIGNMENT, align); - glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(params.MipWidth(mip_map))); - - const auto image_size = static_cast(params.GetMipmapSizeGL(mip_map, false)); - if (tuple.compressed) { - switch (params.target) { - case SurfaceTarget::Texture2D: - glCompressedTextureSubImage2D( - texture.handle, mip_map, 0, 0, static_cast(params.MipWidth(mip_map)), - static_cast(params.MipHeight(mip_map)), tuple.internal_format, image_size, - &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture3D: - glCompressedTextureSubImage3D( - texture.handle, mip_map, 0, 0, 0, static_cast(params.MipWidth(mip_map)), - static_cast(params.MipHeight(mip_map)), - static_cast(params.MipDepth(mip_map)), tuple.internal_format, image_size, - &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glCompressedTextureSubImage3D( - texture.handle, mip_map, 0, 0, 0, static_cast(params.MipWidth(mip_map)), - static_cast(params.MipHeight(mip_map)), static_cast(params.depth), - tuple.internal_format, image_size, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::TextureCubemap: { - const auto layer_size = static_cast(params.LayerSizeGL(mip_map)); - for (std::size_t face = 0; face < params.depth; ++face) { - glCompressedTextureSubImage3D( - texture.handle, mip_map, 0, 0, static_cast(face), - static_cast(params.MipWidth(mip_map)), - static_cast(params.MipHeight(mip_map)), 1, tuple.internal_format, - layer_size, &gl_buffer[mip_map][buffer_offset]); - buffer_offset += layer_size; - } - break; - } - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast(params.target)); - UNREACHABLE(); - glCompressedTextureSubImage2D( - texture.handle, mip_map, 0, 0, static_cast(params.MipWidth(mip_map)), - static_cast(params.MipHeight(mip_map)), tuple.internal_format, - static_cast(params.size_in_bytes_gl), &gl_buffer[mip_map][buffer_offset]); - } - } else { - switch (params.target) { - case SurfaceTarget::Texture1D: - glTextureSubImage1D(texture.handle, mip_map, x0, static_cast(rect.GetWidth()), - tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture2D: - glTextureSubImage2D(texture.handle, mip_map, x0, y0, - static_cast(rect.GetWidth()), - static_cast(rect.GetHeight()), tuple.format, tuple.type, - &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture3D: - glTextureSubImage3D(texture.handle, mip_map, x0, y0, 0, - static_cast(rect.GetWidth()), - static_cast(rect.GetHeight()), params.MipDepth(mip_map), - tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - glTextureSubImage3D(texture.handle, mip_map, x0, y0, 0, - static_cast(rect.GetWidth()), - static_cast(rect.GetHeight()), params.depth, tuple.format, - tuple.type, &gl_buffer[mip_map][buffer_offset]); - break; - case SurfaceTarget::TextureCubemap: { - for (std::size_t face = 0; face < params.depth; ++face) { - glTextureSubImage3D(texture.handle, mip_map, x0, y0, static_cast(face), - static_cast(rect.GetWidth()), - static_cast(rect.GetHeight()), 1, tuple.format, - tuple.type, &gl_buffer[mip_map][buffer_offset]); - buffer_offset += params.LayerSizeGL(mip_map); - } - break; - } - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast(params.target)); - UNREACHABLE(); - glTextureSubImage2D(texture.handle, mip_map, x0, y0, - static_cast(rect.GetWidth()), - static_cast(rect.GetHeight()), tuple.format, tuple.type, - &gl_buffer[mip_map][buffer_offset]); - } - } - - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); -} - -void CachedSurface::EnsureTextureDiscrepantView() { - if (discrepant_view.handle != 0) - return; - - const GLenum target{GetArrayDiscrepantTarget(params.target)}; - ASSERT(target != GL_NONE); - - const GLuint num_layers{target == GL_TEXTURE_CUBE_MAP_ARRAY ? 6u : 1u}; - constexpr GLuint min_layer = 0; - constexpr GLuint min_level = 0; - - glGenTextures(1, &discrepant_view.handle); - glTextureView(discrepant_view.handle, target, texture.handle, gl_internal_format, min_level, - params.max_mip_level, min_layer, num_layers); - ApplyTextureDefaults(discrepant_view.handle, params.max_mip_level); - glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, - reinterpret_cast(swizzle.data())); -} - -MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64)); -void CachedSurface::UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, - GLuint read_fb_handle, GLuint draw_fb_handle) { - MICROPROFILE_SCOPE(OpenGL_TextureUL); - - for (u32 i = 0; i < params.max_mip_level; i++) - UploadGLMipmapTexture(res_cache_tmp_mem, i, read_fb_handle, draw_fb_handle); -} - -void CachedSurface::UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x, - Tegra::Texture::SwizzleSource swizzle_y, - Tegra::Texture::SwizzleSource swizzle_z, - Tegra::Texture::SwizzleSource swizzle_w) { - const GLenum new_x = MaxwellToGL::SwizzleSource(swizzle_x); - const GLenum new_y = MaxwellToGL::SwizzleSource(swizzle_y); - const GLenum new_z = MaxwellToGL::SwizzleSource(swizzle_z); - const GLenum new_w = MaxwellToGL::SwizzleSource(swizzle_w); - if (swizzle[0] == new_x && swizzle[1] == new_y && swizzle[2] == new_z && swizzle[3] == new_w) { - return; - } - swizzle = {new_x, new_y, new_z, new_w}; - const auto swizzle_data = reinterpret_cast(swizzle.data()); - glTextureParameteriv(texture.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); - if (discrepant_view.handle != 0) { - glTextureParameteriv(discrepant_view.handle, GL_TEXTURE_SWIZZLE_RGBA, swizzle_data); - } -} - -RasterizerCacheOpenGL::RasterizerCacheOpenGL(RasterizerOpenGL& rasterizer) - : RasterizerCache{rasterizer} { - read_framebuffer.Create(); - draw_framebuffer.Create(); - copy_pbo.Create(); -} - -Surface RasterizerCacheOpenGL::GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, - const GLShader::SamplerEntry& entry) { - return GetSurface(SurfaceParams::CreateForTexture(config, entry)); -} - -Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) { - auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; - const auto& regs{gpu.regs}; - - if (!gpu.dirty_flags.zeta_buffer) { - return last_depth_buffer; - } - gpu.dirty_flags.zeta_buffer = false; - - if (!regs.zeta.Address() || !regs.zeta_enable) { - return last_depth_buffer = {}; - } - - SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer( - regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format, - regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, - regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; - - return last_depth_buffer = GetSurface(depth_params, preserve_contents); -} - -Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool preserve_contents) { - auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()}; - const auto& regs{gpu.regs}; - - if (!gpu.dirty_flags.color_buffer[index]) { - return current_color_buffers[index]; - } - gpu.dirty_flags.color_buffer.reset(index); - - ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); - - if (index >= regs.rt_control.count) { - return current_color_buffers[index] = {}; - } - - if (regs.rt[index].Address() == 0 || regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { - return current_color_buffers[index] = {}; - } - - const SurfaceParams color_params{SurfaceParams::CreateForFramebuffer(index)}; - - return current_color_buffers[index] = GetSurface(color_params, preserve_contents); -} - -void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { - surface->LoadGLBuffer(temporal_memory); - surface->UploadGLTexture(temporal_memory, read_framebuffer.handle, draw_framebuffer.handle); - surface->MarkAsModified(false, *this); - surface->MarkForReload(false); -} - -Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) { - if (!params.IsValid()) { - return {}; - } - - // Look up surface in the cache based on address - Surface surface{TryGet(params.host_ptr)}; - if (surface) { - if (surface->GetSurfaceParams().IsCompatibleSurface(params)) { - // Use the cached surface as-is unless it's not synced with memory - if (surface->MustReload()) - LoadSurface(surface); - return surface; - } else if (preserve_contents) { - // If surface parameters changed and we care about keeping the previous data, recreate - // the surface from the old one - Surface new_surface{RecreateSurface(surface, params)}; - Unregister(surface); - Register(new_surface); - if (new_surface->IsUploaded()) { - RegisterReinterpretSurface(new_surface); - } - return new_surface; - } else { - // Delete the old surface before creating a new one to prevent collisions. - Unregister(surface); - } - } - - // No cached surface found - get a new one - surface = GetUncachedSurface(params); - Register(surface); - - // Only load surface from memory if we care about the contents - if (preserve_contents) { - LoadSurface(surface); - } - - return surface; -} - -Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) { - Surface surface{TryGetReservedSurface(params)}; - if (!surface) { - // No reserved surface available, create a new one and reserve it - surface = std::make_shared(params); - ReserveSurface(surface); - } - return surface; -} - -void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface, - const Surface& dst_surface) { - const auto& init_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; - GPUVAddr address{init_params.gpu_addr}; - const std::size_t layer_size{dst_params.LayerMemorySize()}; - for (u32 layer = 0; layer < dst_params.depth; layer++) { - for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) { - const GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)}; - const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))}; - if (!copy) { - continue; - } - const auto& src_params{copy->GetSurfaceParams()}; - const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))}; - const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))}; - - glCopyImageSubData(copy->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, - 0, 0, dst_surface->Texture().handle, - SurfaceTargetToGL(dst_params.target), mipmap, 0, 0, layer, width, - height, 1); - } - address += layer_size; - } - - dst_surface->MarkAsModified(true, *this); -} - -static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface, - const Common::Rectangle& src_rect, - const Common::Rectangle& dst_rect, GLuint read_fb_handle, - GLuint draw_fb_handle, GLenum src_attachment = 0, GLenum dst_attachment = 0, - std::size_t cubemap_face = 0) { - - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - OpenGLState prev_state{OpenGLState::GetCurState()}; - SCOPE_EXIT({ prev_state.Apply(); }); - - OpenGLState state; - state.draw.read_framebuffer = read_fb_handle; - state.draw.draw_framebuffer = draw_fb_handle; - state.Apply(); - - u32 buffers{}; - - if (src_params.type == SurfaceType::ColorTexture) { - switch (src_params.target) { - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - case SurfaceTarget::TextureCubemap: - glFramebufferTexture2D( - GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), - src_surface->Texture().handle, 0); - glFramebufferTexture2D( - GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0); - break; - case SurfaceTarget::Texture2DArray: - glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - src_surface->Texture().handle, 0, 0); - glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0); - break; - case SurfaceTarget::Texture3D: - glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - SurfaceTargetToGL(src_params.target), - src_surface->Texture().handle, 0, 0); - glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - SurfaceTargetToGL(src_params.target), 0, 0, 0); - break; - default: - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - } - - switch (dst_params.target) { - case SurfaceTarget::Texture2D: - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - case SurfaceTarget::TextureCubemap: - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), - dst_surface->Texture().handle, 0); - glFramebufferTexture2D( - GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0); - break; - case SurfaceTarget::Texture2DArray: - glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - dst_surface->Texture().handle, 0, 0); - glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0); - break; - - case SurfaceTarget::Texture3D: - glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - SurfaceTargetToGL(dst_params.target), - dst_surface->Texture().handle, 0, 0); - glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, - SurfaceTargetToGL(dst_params.target), 0, 0, 0); - break; - default: - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - 0, 0); - break; - } - - buffers = GL_COLOR_BUFFER_BIT; - } else if (src_params.type == SurfaceType::Depth) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - src_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, - dst_surface->Texture().handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - buffers = GL_DEPTH_BUFFER_BIT; - } else if (src_params.type == SurfaceType::DepthStencil) { - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - src_surface->Texture().handle, 0); - - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment, - GL_TEXTURE_2D, 0, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, - dst_surface->Texture().handle, 0); - - buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; - } - - glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left, - dst_rect.top, dst_rect.right, dst_rect.bottom, buffers, - buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); - - return true; -} - -void RasterizerCacheOpenGL::FermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& src_config, - const Tegra::Engines::Fermi2D::Regs::Surface& dst_config, - const Common::Rectangle& src_rect, const Common::Rectangle& dst_rect) { - - const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config); - const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config); - - ASSERT(src_params.pixel_format == dst_params.pixel_format); - ASSERT(src_params.block_height == dst_params.block_height); - ASSERT(src_params.is_tiled == dst_params.is_tiled); - ASSERT(src_params.depth == dst_params.depth); - ASSERT(src_params.target == dst_params.target); - ASSERT(src_params.rt.index == dst_params.rt.index); - - auto src_surface = GetSurface(src_params, true); - auto dst_surface = GetSurface(dst_params, true); - - BlitSurface(src_surface, dst_surface, src_rect, dst_rect, read_framebuffer.handle, - draw_framebuffer.handle); - - dst_surface->MarkAsModified(true, *this); -} - -void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface, - const Surface& dst_surface) { - const auto& src_params{src_surface->GetSurfaceParams()}; - const auto& dst_params{dst_surface->GetSurfaceParams()}; - - // Flush enough memory for both the source and destination surface - FlushRegion(ToCacheAddr(src_params.host_ptr), - std::max(src_params.MemorySize(), dst_params.MemorySize())); - - LoadSurface(dst_surface); -} - -Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, - const SurfaceParams& new_params) { - // Verify surface is compatible for blitting - auto old_params{old_surface->GetSurfaceParams()}; - - // Get a new surface with the new parameters, and blit the previous surface to it - Surface new_surface{GetUncachedSurface(new_params)}; - - // With use_accurate_gpu_emulation enabled, do an accurate surface copy - if (Settings::values.use_accurate_gpu_emulation) { - AccurateCopySurface(old_surface, new_surface); - return new_surface; - } - - const bool old_compressed = - GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed; - const bool new_compressed = - GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed; - const bool compatible_formats = - GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) && - !(old_compressed || new_compressed); - // For compatible surfaces, we can just do fast glCopyImageSubData based copy - if (old_params.target == new_params.target && old_params.depth == new_params.depth && - old_params.depth == 1 && compatible_formats) { - FastCopySurface(old_surface, new_surface); - return new_surface; - } - - switch (new_params.target) { - case SurfaceTarget::Texture2D: - CopySurface(old_surface, new_surface, copy_pbo.handle); - break; - case SurfaceTarget::Texture3D: - AccurateCopySurface(old_surface, new_surface); - break; - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - if (compatible_formats) - FastLayeredCopySurface(old_surface, new_surface); - else { - AccurateCopySurface(old_surface, new_surface); - } - break; - default: - LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", - static_cast(new_params.target)); - UNREACHABLE(); - } - - return new_surface; -} - -Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(const u8* host_ptr) const { - return TryGet(host_ptr); -} - -void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) { - const auto& surface_reserve_key{SurfaceReserveKey::Create(surface->GetSurfaceParams())}; - surface_reserve[surface_reserve_key] = surface; -} - -Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params) { - const auto& surface_reserve_key{SurfaceReserveKey::Create(params)}; - auto search{surface_reserve.find(surface_reserve_key)}; - if (search != surface_reserve.end()) { - return search->second; - } - return {}; -} - -static std::optional TryFindBestMipMap(std::size_t memory, const SurfaceParams params, - u32 height) { - for (u32 i = 0; i < params.max_mip_level; i++) { - if (memory == params.GetMipmapSingleSize(i) && params.MipHeight(i) == height) { - return {i}; - } - } - return {}; -} - -static std::optional TryFindBestLayer(GPUVAddr addr, const SurfaceParams params, u32 mipmap) { - const std::size_t size{params.LayerMemorySize()}; - GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)}; - for (u32 i = 0; i < params.depth; i++) { - if (start == addr) { - return {i}; - } - start += size; - } - return {}; -} - -static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surface render_surface, - const Surface blitted_surface) { - const auto& dst_params = blitted_surface->GetSurfaceParams(); - const auto& src_params = render_surface->GetSurfaceParams(); - const std::size_t src_memory_size = src_params.size_in_bytes; - const std::optional level = - TryFindBestMipMap(src_memory_size, dst_params, src_params.height); - if (level.has_value()) { - if (src_params.width == dst_params.MipWidthGobAligned(*level) && - src_params.height == dst_params.MipHeight(*level) && - src_params.block_height >= dst_params.MipBlockHeight(*level)) { - const std::optional slot = - TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level); - if (slot.has_value()) { - glCopyImageSubData(render_surface->Texture().handle, - SurfaceTargetToGL(src_params.target), 0, 0, 0, 0, - blitted_surface->Texture().handle, - SurfaceTargetToGL(dst_params.target), *level, 0, 0, *slot, - dst_params.MipWidth(*level), dst_params.MipHeight(*level), 1); - blitted_surface->MarkAsModified(true, cache); - return true; - } - } - } - return false; -} - -static bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) { - const VAddr bound1 = blitted_surface->GetCpuAddr() + blitted_surface->GetMemorySize(); - const VAddr bound2 = render_surface->GetCpuAddr() + render_surface->GetMemorySize(); - if (bound2 > bound1) - return true; - const auto& dst_params = blitted_surface->GetSurfaceParams(); - const auto& src_params = render_surface->GetSurfaceParams(); - return (dst_params.component_type != src_params.component_type); -} - -static bool IsReinterpretInvalidSecond(const Surface render_surface, - const Surface blitted_surface) { - const auto& dst_params = blitted_surface->GetSurfaceParams(); - const auto& src_params = render_surface->GetSurfaceParams(); - return (dst_params.height > src_params.height && dst_params.width > src_params.width); -} - -bool RasterizerCacheOpenGL::PartialReinterpretSurface(Surface triggering_surface, - Surface intersect) { - if (IsReinterpretInvalid(triggering_surface, intersect)) { - Unregister(intersect); - return false; - } - if (!LayerFitReinterpretSurface(*this, triggering_surface, intersect)) { - if (IsReinterpretInvalidSecond(triggering_surface, intersect)) { - Unregister(intersect); - return false; - } - FlushObject(intersect); - FlushObject(triggering_surface); - intersect->MarkForReload(true); - } - return true; -} - -void RasterizerCacheOpenGL::SignalPreDrawCall() { - if (texception && GLAD_GL_ARB_texture_barrier) { - glTextureBarrier(); - } - texception = false; -} - -void RasterizerCacheOpenGL::SignalPostDrawCall() { - for (u32 i = 0; i < Maxwell::NumRenderTargets; i++) { - if (current_color_buffers[i] != nullptr) { - Surface intersect = - CollideOnReinterpretedSurface(current_color_buffers[i]->GetCacheAddr()); - if (intersect != nullptr) { - PartialReinterpretSurface(current_color_buffers[i], intersect); - texception = true; - } - } - } -} - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h deleted file mode 100644 index 6263ef3e75..0000000000 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ /dev/null @@ -1,572 +0,0 @@ -// Copyright 2018 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include - -#include "common/alignment.h" -#include "common/bit_util.h" -#include "common/common_types.h" -#include "common/hash.h" -#include "common/math_util.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/rasterizer_cache.h" -#include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/renderer_opengl/gl_shader_gen.h" -#include "video_core/surface.h" -#include "video_core/textures/decoders.h" -#include "video_core/textures/texture.h" - -namespace OpenGL { - -class CachedSurface; -using Surface = std::shared_ptr; -using SurfaceSurfaceRect_Tuple = std::tuple>; - -using SurfaceTarget = VideoCore::Surface::SurfaceTarget; -using SurfaceType = VideoCore::Surface::SurfaceType; -using PixelFormat = VideoCore::Surface::PixelFormat; -using ComponentType = VideoCore::Surface::ComponentType; -using Maxwell = Tegra::Engines::Maxwell3D::Regs; - -struct SurfaceParams { - enum class SurfaceClass { - Uploaded, - RenderTarget, - DepthBuffer, - Copy, - }; - - static std::string SurfaceTargetName(SurfaceTarget target) { - switch (target) { - case SurfaceTarget::Texture1D: - return "Texture1D"; - case SurfaceTarget::Texture2D: - return "Texture2D"; - case SurfaceTarget::Texture3D: - return "Texture3D"; - case SurfaceTarget::Texture1DArray: - return "Texture1DArray"; - case SurfaceTarget::Texture2DArray: - return "Texture2DArray"; - case SurfaceTarget::TextureCubemap: - return "TextureCubemap"; - case SurfaceTarget::TextureCubeArray: - return "TextureCubeArray"; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast(target)); - UNREACHABLE(); - return fmt::format("TextureUnknown({})", static_cast(target)); - } - } - - u32 GetFormatBpp() const { - return VideoCore::Surface::GetFormatBpp(pixel_format); - } - - /// Returns the rectangle corresponding to this surface - Common::Rectangle GetRect(u32 mip_level = 0) const; - - /// Returns the total size of this surface in bytes, adjusted for compression - std::size_t SizeInBytesRaw(bool ignore_tiled = false) const { - const u32 compression_factor{GetCompressionFactor(pixel_format)}; - const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)}; - const size_t uncompressed_size{ - Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width, - height, depth, block_height, block_depth)}; - - // Divide by compression_factor^2, as height and width are factored by this - return uncompressed_size / (compression_factor * compression_factor); - } - - /// Returns the size of this surface as an OpenGL texture in bytes - std::size_t SizeInBytesGL() const { - return SizeInBytesRaw(true); - } - - /// Returns the size of this surface as a cube face in bytes - std::size_t SizeInBytesCubeFace() const { - return size_in_bytes / 6; - } - - /// Returns the size of this surface as an OpenGL cube face in bytes - std::size_t SizeInBytesCubeFaceGL() const { - return size_in_bytes_gl / 6; - } - - /// Returns the exact size of memory occupied by the texture in VRAM, including mipmaps. - std::size_t MemorySize() const { - std::size_t size = InnerMemorySize(false, is_layered); - if (is_layered) - return size * depth; - return size; - } - - /// Returns true if the parameters constitute a valid rasterizer surface. - bool IsValid() const { - return gpu_addr && host_ptr && height && width; - } - - /// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including - /// mipmaps. - std::size_t LayerMemorySize() const { - return InnerMemorySize(false, true); - } - - /// Returns the size of a layer of this surface in OpenGL. - std::size_t LayerSizeGL(u32 mip_level) const { - return InnerMipmapMemorySize(mip_level, true, is_layered, false); - } - - std::size_t GetMipmapSizeGL(u32 mip_level, bool ignore_compressed = true) const { - std::size_t size = InnerMipmapMemorySize(mip_level, true, is_layered, ignore_compressed); - if (is_layered) - return size * depth; - return size; - } - - std::size_t GetMipmapLevelOffset(u32 mip_level) const { - std::size_t offset = 0; - for (u32 i = 0; i < mip_level; i++) - offset += InnerMipmapMemorySize(i, false, is_layered); - return offset; - } - - std::size_t GetMipmapLevelOffsetGL(u32 mip_level) const { - std::size_t offset = 0; - for (u32 i = 0; i < mip_level; i++) - offset += InnerMipmapMemorySize(i, true, is_layered); - return offset; - } - - std::size_t GetMipmapSingleSize(u32 mip_level) const { - return InnerMipmapMemorySize(mip_level, false, is_layered); - } - - u32 MipWidth(u32 mip_level) const { - return std::max(1U, width >> mip_level); - } - - u32 MipWidthGobAligned(u32 mip_level) const { - return Common::AlignUp(std::max(1U, width >> mip_level), 64U * 8U / GetFormatBpp()); - } - - u32 MipHeight(u32 mip_level) const { - return std::max(1U, height >> mip_level); - } - - u32 MipDepth(u32 mip_level) const { - return is_layered ? depth : std::max(1U, depth >> mip_level); - } - - // Auto block resizing algorithm from: - // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c - u32 MipBlockHeight(u32 mip_level) const { - if (mip_level == 0) - return block_height; - u32 alt_height = MipHeight(mip_level); - u32 h = GetDefaultBlockHeight(pixel_format); - u32 blocks_in_y = (alt_height + h - 1) / h; - u32 bh = 16; - while (bh > 1 && blocks_in_y <= bh * 4) { - bh >>= 1; - } - return bh; - } - - u32 MipBlockDepth(u32 mip_level) const { - if (mip_level == 0) { - return block_depth; - } - - if (is_layered) { - return 1; - } - - const u32 mip_depth = MipDepth(mip_level); - u32 bd = 32; - while (bd > 1 && mip_depth * 2 <= bd) { - bd >>= 1; - } - - if (bd == 32) { - const u32 bh = MipBlockHeight(mip_level); - if (bh >= 4) { - return 16; - } - } - - return bd; - } - - u32 RowAlign(u32 mip_level) const { - const u32 m_width = MipWidth(mip_level); - const u32 bytes_per_pixel = GetBytesPerPixel(pixel_format); - const u32 l2 = Common::CountTrailingZeroes32(m_width * bytes_per_pixel); - return (1U << l2); - } - - /// Creates SurfaceParams from a texture configuration - static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config, - const GLShader::SamplerEntry& entry); - - /// Creates SurfaceParams from a framebuffer configuration - static SurfaceParams CreateForFramebuffer(std::size_t index); - - /// Creates SurfaceParams for a depth buffer configuration - static SurfaceParams CreateForDepthBuffer( - u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); - - /// Creates SurfaceParams for a Fermi2D surface copy - static SurfaceParams CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config); - - /// Checks if surfaces are compatible for caching - bool IsCompatibleSurface(const SurfaceParams& other) const { - if (std::tie(pixel_format, type, width, height, target, depth, is_tiled) == - std::tie(other.pixel_format, other.type, other.width, other.height, other.target, - other.depth, other.is_tiled)) { - if (!is_tiled) - return true; - return std::tie(block_height, block_depth, tile_width_spacing) == - std::tie(other.block_height, other.block_depth, other.tile_width_spacing); - } - return false; - } - - /// Initializes parameters for caching, should be called after everything has been initialized - void InitCacheParameters(GPUVAddr gpu_addr); - - std::string TargetName() const { - switch (target) { - case SurfaceTarget::Texture1D: - return "1D"; - case SurfaceTarget::Texture2D: - return "2D"; - case SurfaceTarget::Texture3D: - return "3D"; - case SurfaceTarget::Texture1DArray: - return "1DArray"; - case SurfaceTarget::Texture2DArray: - return "2DArray"; - case SurfaceTarget::TextureCubemap: - return "Cube"; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast(target)); - UNREACHABLE(); - return fmt::format("TUK({})", static_cast(target)); - } - } - - std::string ClassName() const { - switch (identity) { - case SurfaceClass::Uploaded: - return "UP"; - case SurfaceClass::RenderTarget: - return "RT"; - case SurfaceClass::DepthBuffer: - return "DB"; - case SurfaceClass::Copy: - return "CP"; - default: - LOG_CRITICAL(HW_GPU, "Unimplemented surface_class={}", static_cast(identity)); - UNREACHABLE(); - return fmt::format("CUK({})", static_cast(identity)); - } - } - - std::string IdentityString() const { - return ClassName() + '_' + TargetName() + '_' + (is_tiled ? 'T' : 'L'); - } - - bool is_tiled; - u32 block_width; - u32 block_height; - u32 block_depth; - u32 tile_width_spacing; - PixelFormat pixel_format; - ComponentType component_type; - SurfaceType type; - u32 width; - u32 height; - u32 depth; - u32 unaligned_height; - u32 pitch; - SurfaceTarget target; - SurfaceClass identity; - u32 max_mip_level; - bool is_layered; - bool is_array; - bool srgb_conversion; - // Parameters used for caching - u8* host_ptr; - GPUVAddr gpu_addr; - std::size_t size_in_bytes; - std::size_t size_in_bytes_gl; - - // Render target specific parameters, not used in caching - struct { - u32 index; - u32 array_mode; - u32 volume; - u32 layer_stride; - u32 base_layer; - } rt; - -private: - std::size_t InnerMipmapMemorySize(u32 mip_level, bool force_gl = false, bool layer_only = false, - bool uncompressed = false) const; - std::size_t InnerMemorySize(bool force_gl = false, bool layer_only = false, - bool uncompressed = false) const; -}; - -}; // namespace OpenGL - -/// Hashable variation of SurfaceParams, used for a key in the surface cache -struct SurfaceReserveKey : Common::HashableStruct { - static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) { - SurfaceReserveKey res; - res.state = params; - res.state.identity = {}; // Ignore the origin of the texture - res.state.gpu_addr = {}; // Ignore GPU vaddr in caching - res.state.rt = {}; // Ignore rt config in caching - return res; - } -}; -namespace std { -template <> -struct hash { - std::size_t operator()(const SurfaceReserveKey& k) const { - return k.Hash(); - } -}; -} // namespace std - -namespace OpenGL { - -class RasterizerOpenGL; - -// This is used to store temporary big buffers, -// instead of creating/destroying all the time -struct RasterizerTemporaryMemory { - std::vector> gl_buffer; -}; - -class CachedSurface final : public RasterizerCacheObject { -public: - explicit CachedSurface(const SurfaceParams& params); - - VAddr GetCpuAddr() const override { - return cpu_addr; - } - - std::size_t GetSizeInBytes() const override { - return cached_size_in_bytes; - } - - std::size_t GetMemorySize() const { - return memory_size; - } - - const OGLTexture& Texture() const { - return texture; - } - - const OGLTexture& Texture(bool as_array) { - if (params.is_array == as_array) { - return texture; - } else { - EnsureTextureDiscrepantView(); - return discrepant_view; - } - } - - GLenum Target() const { - return gl_target; - } - - const SurfaceParams& GetSurfaceParams() const { - return params; - } - - // Read/Write data in Switch memory to/from gl_buffer - void LoadGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem); - void FlushGLBuffer(RasterizerTemporaryMemory& res_cache_tmp_mem); - - // Upload data in gl_buffer to this surface's texture - void UploadGLTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, GLuint read_fb_handle, - GLuint draw_fb_handle); - - void UpdateSwizzle(Tegra::Texture::SwizzleSource swizzle_x, - Tegra::Texture::SwizzleSource swizzle_y, - Tegra::Texture::SwizzleSource swizzle_z, - Tegra::Texture::SwizzleSource swizzle_w); - - void MarkReinterpreted() { - reinterpreted = true; - } - - bool IsReinterpreted() const { - return reinterpreted; - } - - void MarkForReload(bool reload) { - must_reload = reload; - } - - bool MustReload() const { - return must_reload; - } - - bool IsUploaded() const { - return params.identity == SurfaceParams::SurfaceClass::Uploaded; - } - -private: - void UploadGLMipmapTexture(RasterizerTemporaryMemory& res_cache_tmp_mem, u32 mip_map, - GLuint read_fb_handle, GLuint draw_fb_handle); - - void EnsureTextureDiscrepantView(); - - OGLTexture texture; - OGLTexture discrepant_view; - SurfaceParams params{}; - GLenum gl_target{}; - GLenum gl_internal_format{}; - std::size_t cached_size_in_bytes{}; - std::array swizzle{GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; - std::size_t memory_size; - bool reinterpreted = false; - bool must_reload = false; - VAddr cpu_addr{}; -}; - -class RasterizerCacheOpenGL final : public RasterizerCache { -public: - explicit RasterizerCacheOpenGL(RasterizerOpenGL& rasterizer); - - /// Get a surface based on the texture configuration - Surface GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, - const GLShader::SamplerEntry& entry); - - /// Get the depth surface based on the framebuffer configuration - Surface GetDepthBufferSurface(bool preserve_contents); - - /// Get the color surface based on the framebuffer configuration and the specified render target - Surface GetColorBufferSurface(std::size_t index, bool preserve_contents); - - /// Tries to find a framebuffer using on the provided CPU address - Surface TryFindFramebufferSurface(const u8* host_ptr) const; - - /// Copies the contents of one surface to another - void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config, - const Tegra::Engines::Fermi2D::Regs::Surface& dst_config, - const Common::Rectangle& src_rect, - const Common::Rectangle& dst_rect); - - void SignalPreDrawCall(); - void SignalPostDrawCall(); - -protected: - void FlushObjectInner(const Surface& object) override { - object->FlushGLBuffer(temporal_memory); - } - -private: - void LoadSurface(const Surface& surface); - Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true); - - /// Gets an uncached surface, creating it if need be - Surface GetUncachedSurface(const SurfaceParams& params); - - /// Recreates a surface with new parameters - Surface RecreateSurface(const Surface& old_surface, const SurfaceParams& new_params); - - /// Reserves a unique surface that can be reused later - void ReserveSurface(const Surface& surface); - - /// Tries to get a reserved surface for the specified parameters - Surface TryGetReservedSurface(const SurfaceParams& params); - - // Partialy reinterpret a surface based on a triggering_surface that collides with it. - // returns true if the reinterpret was successful, false in case it was not. - bool PartialReinterpretSurface(Surface triggering_surface, Surface intersect); - - /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data - void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface); - void FastLayeredCopySurface(const Surface& src_surface, const Surface& dst_surface); - void FastCopySurface(const Surface& src_surface, const Surface& dst_surface); - void CopySurface(const Surface& src_surface, const Surface& dst_surface, - const GLuint copy_pbo_handle, const GLenum src_attachment = 0, - const GLenum dst_attachment = 0, const std::size_t cubemap_face = 0); - - /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have - /// previously been used. This is to prevent surfaces from being constantly created and - /// destroyed when used with different surface parameters. - std::unordered_map surface_reserve; - - OGLFramebuffer read_framebuffer; - OGLFramebuffer draw_framebuffer; - - bool texception = false; - - /// Use a Pixel Buffer Object to download the previous texture and then upload it to the new one - /// using the new format. - OGLBuffer copy_pbo; - - std::array last_color_buffers; - std::array current_color_buffers; - Surface last_depth_buffer; - - RasterizerTemporaryMemory temporal_memory; - - using SurfaceIntervalCache = boost::icl::interval_map; - using SurfaceInterval = typename SurfaceIntervalCache::interval_type; - - static auto GetReinterpretInterval(const Surface& object) { - return SurfaceInterval::right_open(object->GetCacheAddr() + 1, - object->GetCacheAddr() + object->GetMemorySize() - 1); - } - - // Reinterpreted surfaces are very fragil as the game may keep rendering into them. - SurfaceIntervalCache reinterpreted_surfaces; - - void RegisterReinterpretSurface(Surface reinterpret_surface) { - auto interval = GetReinterpretInterval(reinterpret_surface); - reinterpreted_surfaces.insert({interval, reinterpret_surface}); - reinterpret_surface->MarkReinterpreted(); - } - - Surface CollideOnReinterpretedSurface(CacheAddr addr) const { - const SurfaceInterval interval{addr}; - for (auto& pair : - boost::make_iterator_range(reinterpreted_surfaces.equal_range(interval))) { - return pair.second; - } - return nullptr; - } - - void Register(const Surface& object) override { - RasterizerCache::Register(object); - } - - /// Unregisters an object from the cache - void Unregister(const Surface& object) override { - if (object->IsReinterpreted()) { - auto interval = GetReinterpretInterval(object); - reinterpreted_surfaces.erase(interval); - } - RasterizerCache::Unregister(object); - } -}; - -} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index bfe666a73f..5c96c1d462 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -33,6 +33,24 @@ void OGLTexture::Release() { handle = 0; } +void OGLTextureView::Create() { + if (handle != 0) + return; + + MICROPROFILE_SCOPE(OpenGL_ResourceCreation); + glGenTextures(1, &handle); +} + +void OGLTextureView::Release() { + if (handle == 0) + return; + + MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); + glDeleteTextures(1, &handle); + OpenGLState::GetCurState().UnbindTexture(handle).Apply(); + handle = 0; +} + void OGLSampler::Create() { if (handle != 0) return; @@ -130,6 +148,12 @@ void OGLBuffer::Release() { handle = 0; } +void OGLBuffer::MakeStreamCopy(std::size_t buffer_size) { + ASSERT_OR_EXECUTE((handle != 0 && buffer_size != 0), { return; }); + + glNamedBufferData(handle, buffer_size, nullptr, GL_STREAM_COPY); +} + void OGLSync::Create() { if (handle != 0) return; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index fbb93ee499..3a85a1d4c1 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -36,6 +36,31 @@ public: GLuint handle = 0; }; +class OGLTextureView : private NonCopyable { +public: + OGLTextureView() = default; + + OGLTextureView(OGLTextureView&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + + ~OGLTextureView() { + Release(); + } + + OGLTextureView& operator=(OGLTextureView&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create(); + + /// Deletes the internal OpenGL resource + void Release(); + + GLuint handle = 0; +}; + class OGLSampler : private NonCopyable { public: OGLSampler() = default; @@ -161,6 +186,9 @@ public: /// Deletes the internal OpenGL resource void Release(); + // Converts the buffer into a stream copy buffer with a fixed size + void MakeStreamCopy(std::size_t buffer_size); + GLuint handle = 0; }; diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 8d3d7bfdc0..f9b2b03a0a 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp @@ -103,15 +103,22 @@ constexpr std::tuple GetPrimitiveDescription(GLen /// Calculates the size of a program stream std::size_t CalculateProgramSize(const GLShader::ProgramCode& program) { constexpr std::size_t start_offset = 10; + // This is the encoded version of BRA that jumps to itself. All Nvidia + // shaders end with one. + constexpr u64 self_jumping_branch = 0xE2400FFFFF07000FULL; + constexpr u64 mask = 0xFFFFFFFFFF7FFFFFULL; std::size_t offset = start_offset; std::size_t size = start_offset * sizeof(u64); while (offset < program.size()) { const u64 instruction = program[offset]; if (!IsSchedInstruction(offset, start_offset)) { - if (instruction == 0 || (instruction >> 52) == 0x50b) { + if ((instruction & mask) == self_jumping_branch) { // End on Maxwell's "nop" instruction break; } + if (instruction == 0) { + break; + } } size += sizeof(u64); offset++; @@ -168,8 +175,12 @@ GLShader::ProgramResult CreateProgram(const Device& device, Maxwell::ShaderProgr } CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEntries& entries, - Maxwell::ShaderProgram program_type, BaseBindings base_bindings, - GLenum primitive_mode, bool hint_retrievable = false) { + Maxwell::ShaderProgram program_type, const ProgramVariant& variant, + bool hint_retrievable = false) { + auto base_bindings{variant.base_bindings}; + const auto primitive_mode{variant.primitive_mode}; + const auto texture_buffer_usage{variant.texture_buffer_usage}; + std::string source = "#version 430 core\n" "#extension GL_ARB_separate_shader_objects : enable\n\n"; source += fmt::format("#define EMULATION_UBO_BINDING {}\n", base_bindings.cbuf++); @@ -186,6 +197,18 @@ CachedProgram SpecializeShader(const std::string& code, const GLShader::ShaderEn source += fmt::format("#define SAMPLER_BINDING_{} {}\n", sampler.GetIndex(), base_bindings.sampler++); } + for (const auto& image : entries.images) { + source += + fmt::format("#define IMAGE_BINDING_{} {}\n", image.GetIndex(), base_bindings.image++); + } + + // Transform 1D textures to texture samplers by declaring its preprocessor macros. + for (std::size_t i = 0; i < texture_buffer_usage.size(); ++i) { + if (!texture_buffer_usage.test(i)) { + continue; + } + source += fmt::format("#define SAMPLER_{}_IS_BUFFER", i); + } if (program_type == Maxwell::ShaderProgram::Geometry) { const auto [glsl_topology, debug_name, max_vertices] = @@ -254,20 +277,18 @@ Shader CachedShader::CreateStageFromCache(const ShaderParameters& params, return std::shared_ptr(new CachedShader(params, program_type, std::move(result))); } -std::tuple CachedShader::GetProgramHandle(GLenum primitive_mode, - BaseBindings base_bindings) { +std::tuple CachedShader::GetProgramHandle(const ProgramVariant& variant) { GLuint handle{}; if (program_type == Maxwell::ShaderProgram::Geometry) { - handle = GetGeometryShader(primitive_mode, base_bindings); + handle = GetGeometryShader(variant); } else { - const auto [entry, is_cache_miss] = programs.try_emplace(base_bindings); + const auto [entry, is_cache_miss] = programs.try_emplace(variant); auto& program = entry->second; if (is_cache_miss) { - program = TryLoadProgram(primitive_mode, base_bindings); + program = TryLoadProgram(variant); if (!program) { - program = - SpecializeShader(code, entries, program_type, base_bindings, primitive_mode); - disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); + program = SpecializeShader(code, entries, program_type, variant); + disk_cache.SaveUsage(GetUsage(variant)); } LabelGLObject(GL_PROGRAM, program->handle, cpu_addr); @@ -276,6 +297,7 @@ std::tuple CachedShader::GetProgramHandle(GLenum primitive handle = program->handle; } + auto base_bindings{variant.base_bindings}; base_bindings.cbuf += static_cast(entries.const_buffers.size()) + RESERVED_UBOS; base_bindings.gmem += static_cast(entries.global_memory_entries.size()); base_bindings.sampler += static_cast(entries.samplers.size()); @@ -283,43 +305,42 @@ std::tuple CachedShader::GetProgramHandle(GLenum primitive return {handle, base_bindings}; } -GLuint CachedShader::GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings) { - const auto [entry, is_cache_miss] = geometry_programs.try_emplace(base_bindings); +GLuint CachedShader::GetGeometryShader(const ProgramVariant& variant) { + const auto [entry, is_cache_miss] = geometry_programs.try_emplace(variant); auto& programs = entry->second; - switch (primitive_mode) { + switch (variant.primitive_mode) { case GL_POINTS: - return LazyGeometryProgram(programs.points, base_bindings, primitive_mode); + return LazyGeometryProgram(programs.points, variant); case GL_LINES: case GL_LINE_STRIP: - return LazyGeometryProgram(programs.lines, base_bindings, primitive_mode); + return LazyGeometryProgram(programs.lines, variant); case GL_LINES_ADJACENCY: case GL_LINE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.lines_adjacency, base_bindings, primitive_mode); + return LazyGeometryProgram(programs.lines_adjacency, variant); case GL_TRIANGLES: case GL_TRIANGLE_STRIP: case GL_TRIANGLE_FAN: - return LazyGeometryProgram(programs.triangles, base_bindings, primitive_mode); + return LazyGeometryProgram(programs.triangles, variant); case GL_TRIANGLES_ADJACENCY: case GL_TRIANGLE_STRIP_ADJACENCY: - return LazyGeometryProgram(programs.triangles_adjacency, base_bindings, primitive_mode); + return LazyGeometryProgram(programs.triangles_adjacency, variant); default: UNREACHABLE_MSG("Unknown primitive mode."); - return LazyGeometryProgram(programs.points, base_bindings, primitive_mode); + return LazyGeometryProgram(programs.points, variant); } } -GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings, - GLenum primitive_mode) { +GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, + const ProgramVariant& variant) { if (target_program) { return target_program->handle; } - const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(primitive_mode); - target_program = TryLoadProgram(primitive_mode, base_bindings); + const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(variant.primitive_mode); + target_program = TryLoadProgram(variant); if (!target_program) { - target_program = - SpecializeShader(code, entries, program_type, base_bindings, primitive_mode); - disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings)); + target_program = SpecializeShader(code, entries, program_type, variant); + disk_cache.SaveUsage(GetUsage(variant)); } LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name); @@ -327,18 +348,19 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind return target_program->handle; }; -CachedProgram CachedShader::TryLoadProgram(GLenum primitive_mode, - BaseBindings base_bindings) const { - const auto found = precompiled_programs.find(GetUsage(primitive_mode, base_bindings)); +CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { + const auto found = precompiled_programs.find(GetUsage(variant)); if (found == precompiled_programs.end()) { return {}; } return found->second; } -ShaderDiskCacheUsage CachedShader::GetUsage(GLenum primitive_mode, - BaseBindings base_bindings) const { - return {unique_identifier, base_bindings, primitive_mode}; +ShaderDiskCacheUsage CachedShader::GetUsage(const ProgramVariant& variant) const { + ShaderDiskCacheUsage usage; + usage.unique_identifier = unique_identifier; + usage.variant = variant; + return usage; } ShaderCacheOpenGL::ShaderCacheOpenGL(RasterizerOpenGL& rasterizer, Core::System& system, @@ -404,8 +426,7 @@ void ShaderCacheOpenGL::LoadDiskCache(const std::atomic_bool& stop_loading, } if (!shader) { shader = SpecializeShader(unspecialized.code, unspecialized.entries, - unspecialized.program_type, usage.bindings, - usage.primitive, true); + unspecialized.program_type, usage.variant, true); } std::scoped_lock lock(mutex); diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 01af9b28a2..bbb53cdf4e 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -74,8 +75,7 @@ public: } /// Gets the GL program handle for the shader - std::tuple GetProgramHandle(GLenum primitive_mode, - BaseBindings base_bindings); + std::tuple GetProgramHandle(const ProgramVariant& variant); private: explicit CachedShader(const ShaderParameters& params, Maxwell::ShaderProgram program_type, @@ -92,15 +92,14 @@ private: CachedProgram triangles_adjacency; }; - GLuint GetGeometryShader(GLenum primitive_mode, BaseBindings base_bindings); + GLuint GetGeometryShader(const ProgramVariant& variant); /// Generates a geometry shader or returns one that already exists. - GLuint LazyGeometryProgram(CachedProgram& target_program, BaseBindings base_bindings, - GLenum primitive_mode); + GLuint LazyGeometryProgram(CachedProgram& target_program, const ProgramVariant& variant); - CachedProgram TryLoadProgram(GLenum primitive_mode, BaseBindings base_bindings) const; + CachedProgram TryLoadProgram(const ProgramVariant& variant) const; - ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const; + ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; u8* host_ptr{}; VAddr cpu_addr{}; @@ -113,8 +112,8 @@ private: std::string code; std::size_t shader_length{}; - std::unordered_map programs; - std::unordered_map geometry_programs; + std::unordered_map programs; + std::unordered_map geometry_programs; std::unordered_map cbuf_resource_cache; std::unordered_map gmem_resource_cache; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 7dc2e05604..5f2f1510cd 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -180,6 +180,7 @@ public: DeclareGlobalMemory(); DeclareSamplers(); DeclarePhysicalAttributeReader(); + DeclareImages(); code.AddLine("void execute_{}() {{", suffix); ++code.scope; @@ -234,6 +235,9 @@ public: for (const auto& sampler : ir.GetSamplers()) { entries.samplers.emplace_back(sampler); } + for (const auto& image : ir.GetImages()) { + entries.images.emplace_back(image); + } for (const auto& gmem_pair : ir.GetGlobalMemory()) { const auto& [base, usage] = gmem_pair; entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, @@ -453,9 +457,13 @@ private: void DeclareSamplers() { const auto& samplers = ir.GetSamplers(); for (const auto& sampler : samplers) { - std::string sampler_type = [&sampler] { + const std::string name{GetSampler(sampler)}; + const std::string description{"layout (binding = SAMPLER_BINDING_" + + std::to_string(sampler.GetIndex()) + ") uniform"}; + std::string sampler_type = [&]() { switch (sampler.GetType()) { case Tegra::Shader::TextureType::Texture1D: + // Special cased, read below. return "sampler1D"; case Tegra::Shader::TextureType::Texture2D: return "sampler2D"; @@ -475,8 +483,19 @@ private: sampler_type += "Shadow"; } - code.AddLine("layout (binding = SAMPLER_BINDING_{}) uniform {} {};", sampler.GetIndex(), - sampler_type, GetSampler(sampler)); + if (sampler.GetType() == Tegra::Shader::TextureType::Texture1D) { + // 1D textures can be aliased to texture buffers, hide the declarations behind a + // preprocessor flag and use one or the other from the GPU state. This has to be + // done because shaders don't have enough information to determine the texture type. + EmitIfdefIsBuffer(sampler); + code.AddLine("{} samplerBuffer {};", description, name); + code.AddLine("#else"); + code.AddLine("{} {} {};", description, sampler_type, name); + code.AddLine("#endif"); + } else { + // The other texture types (2D, 3D and cubes) don't have this issue. + code.AddLine("{} {} {};", description, sampler_type, name); + } } if (!samplers.empty()) { code.AddNewLine(); @@ -516,6 +535,37 @@ private: code.AddNewLine(); } + void DeclareImages() { + const auto& images{ir.GetImages()}; + for (const auto& image : images) { + const std::string image_type = [&]() { + switch (image.GetType()) { + case Tegra::Shader::ImageType::Texture1D: + return "image1D"; + case Tegra::Shader::ImageType::TextureBuffer: + return "bufferImage"; + case Tegra::Shader::ImageType::Texture1DArray: + return "image1DArray"; + case Tegra::Shader::ImageType::Texture2D: + return "image2D"; + case Tegra::Shader::ImageType::Texture2DArray: + return "image2DArray"; + case Tegra::Shader::ImageType::Texture3D: + return "image3D"; + default: + UNREACHABLE(); + return "image1D"; + } + }(); + code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform " + "{} {};", + image.GetIndex(), image_type, GetImage(image)); + } + if (!images.empty()) { + code.AddNewLine(); + } + } + void VisitBlock(const NodeBlock& bb) { for (const auto& node : bb) { if (const std::string expr = Visit(node); !expr.empty()) { @@ -1439,13 +1489,61 @@ private: else if (next < count) expr += ", "; } + + // Store a copy of the expression without the lod to be used with texture buffers + std::string expr_buffer = expr; + if (meta->lod) { expr += ", "; expr += CastOperand(Visit(meta->lod), Type::Int); } expr += ')'; + expr += GetSwizzle(meta->element); - return expr + GetSwizzle(meta->element); + expr_buffer += ')'; + expr_buffer += GetSwizzle(meta->element); + + const std::string tmp{code.GenerateTemporary()}; + EmitIfdefIsBuffer(meta->sampler); + code.AddLine("float {} = {};", tmp, expr_buffer); + code.AddLine("#else"); + code.AddLine("float {} = {};", tmp, expr); + code.AddLine("#endif"); + + return tmp; + } + + std::string ImageStore(Operation operation) { + constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; + const auto meta{std::get(operation.GetMeta())}; + + std::string expr = "imageStore("; + expr += GetImage(meta.image); + expr += ", "; + + const std::size_t coords_count{operation.GetOperandsCount()}; + expr += constructors.at(coords_count - 1); + for (std::size_t i = 0; i < coords_count; ++i) { + expr += VisitOperand(operation, i, Type::Int); + if (i + 1 < coords_count) { + expr += ", "; + } + } + expr += "), "; + + const std::size_t values_count{meta.values.size()}; + UNIMPLEMENTED_IF(values_count != 4); + expr += "vec4("; + for (std::size_t i = 0; i < values_count; ++i) { + expr += Visit(meta.values.at(i)); + if (i + 1 < values_count) { + expr += ", "; + } + } + expr += "));"; + + code.AddLine(expr); + return {}; } std::string Branch(Operation operation) { @@ -1688,6 +1786,8 @@ private: &GLSLDecompiler::TextureQueryLod, &GLSLDecompiler::TexelFetch, + &GLSLDecompiler::ImageStore, + &GLSLDecompiler::Branch, &GLSLDecompiler::PushFlowStack, &GLSLDecompiler::PopFlowStack, @@ -1756,6 +1856,14 @@ private: return GetDeclarationWithSuffix(static_cast(sampler.GetIndex()), "sampler"); } + std::string GetImage(const Image& image) const { + return GetDeclarationWithSuffix(static_cast(image.GetIndex()), "image"); + } + + void EmitIfdefIsBuffer(const Sampler& sampler) { + code.AddLine("#ifdef SAMPLER_{}_IS_BUFFER", sampler.GetIndex()); + } + std::string GetDeclarationWithSuffix(u32 index, const std::string& name) const { return fmt::format("{}_{}_{}", name, index, suffix); } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h index c1569e7371..14d11c7fc8 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.h +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h @@ -27,6 +27,7 @@ struct ShaderEntries; using Maxwell = Tegra::Engines::Maxwell3D::Regs; using ProgramResult = std::pair; using SamplerEntry = VideoCommon::Shader::Sampler; +using ImageEntry = VideoCommon::Shader::Image; class ConstBufferEntry : public VideoCommon::Shader::ConstBuffer { public: @@ -74,6 +75,7 @@ struct ShaderEntries { std::vector const_buffers; std::vector samplers; std::vector bindless_samplers; + std::vector images; std::vector global_memory_entries; std::array clip_distances{}; std::size_t shader_length{}; diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index ee4a45ca22..10688397bc 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -34,11 +34,11 @@ enum class PrecompiledEntryKind : u32 { Dump, }; -constexpr u32 NativeVersion = 1; +constexpr u32 NativeVersion = 4; // Making sure sizes doesn't change by accident -static_assert(sizeof(BaseBindings) == 12); -static_assert(sizeof(ShaderDiskCacheUsage) == 24); +static_assert(sizeof(BaseBindings) == 16); +static_assert(sizeof(ShaderDiskCacheUsage) == 40); namespace { @@ -332,11 +332,28 @@ std::optional ShaderDiskCacheOpenGL::LoadDecompiledEn static_cast(type), is_array, is_shadow, is_bindless); } + u32 images_count{}; + if (!LoadObjectFromPrecompiled(images_count)) { + return {}; + } + for (u32 i = 0; i < images_count; ++i) { + u64 offset{}; + u64 index{}; + u32 type{}; + u8 is_bindless{}; + if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || + !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) { + return {}; + } + entry.entries.images.emplace_back( + static_cast(offset), static_cast(index), + static_cast(type), is_bindless != 0); + } + u32 global_memory_count{}; if (!LoadObjectFromPrecompiled(global_memory_count)) { return {}; } - for (u32 i = 0; i < global_memory_count; ++i) { u32 cbuf_index{}; u32 cbuf_offset{}; @@ -360,7 +377,6 @@ std::optional ShaderDiskCacheOpenGL::LoadDecompiledEn if (!LoadObjectFromPrecompiled(shader_length)) { return {}; } - entry.entries.shader_length = static_cast(shader_length); return entry; @@ -400,6 +416,18 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std: } } + if (!SaveObjectToPrecompiled(static_cast(entries.images.size()))) { + return false; + } + for (const auto& image : entries.images) { + if (!SaveObjectToPrecompiled(static_cast(image.GetOffset())) || + !SaveObjectToPrecompiled(static_cast(image.GetIndex())) || + !SaveObjectToPrecompiled(static_cast(image.GetType())) || + !SaveObjectToPrecompiled(static_cast(image.IsBindless() ? 1 : 0))) { + return false; + } + } + if (!SaveObjectToPrecompiled(static_cast(entries.global_memory_entries.size()))) { return false; } diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.h b/src/video_core/renderer_opengl/gl_shader_disk_cache.h index ecd72ba58e..4f296dda6e 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -30,22 +31,26 @@ class IOFile; namespace OpenGL { -using ProgramCode = std::vector; -using Maxwell = Tegra::Engines::Maxwell3D::Regs; - struct ShaderDiskCacheUsage; struct ShaderDiskCacheDump; using ShaderDumpsMap = std::unordered_map; -/// Allocated bindings used by an OpenGL shader program +using ProgramCode = std::vector; +using Maxwell = Tegra::Engines::Maxwell3D::Regs; + +using TextureBufferUsage = std::bitset<64>; + +/// Allocated bindings used by an OpenGL shader program. struct BaseBindings { u32 cbuf{}; u32 gmem{}; u32 sampler{}; + u32 image{}; bool operator==(const BaseBindings& rhs) const { - return std::tie(cbuf, gmem, sampler) == std::tie(rhs.cbuf, rhs.gmem, rhs.sampler); + return std::tie(cbuf, gmem, sampler, image) == + std::tie(rhs.cbuf, rhs.gmem, rhs.sampler, rhs.image); } bool operator!=(const BaseBindings& rhs) const { @@ -53,15 +58,29 @@ struct BaseBindings { } }; -/// Describes how a shader is used +/// Describes the different variants a single program can be compiled. +struct ProgramVariant { + BaseBindings base_bindings; + GLenum primitive_mode{}; + TextureBufferUsage texture_buffer_usage{}; + + bool operator==(const ProgramVariant& rhs) const { + return std::tie(base_bindings, primitive_mode, texture_buffer_usage) == + std::tie(rhs.base_bindings, rhs.primitive_mode, rhs.texture_buffer_usage); + } + + bool operator!=(const ProgramVariant& rhs) const { + return !operator==(rhs); + } +}; + +/// Describes how a shader is used. struct ShaderDiskCacheUsage { u64 unique_identifier{}; - BaseBindings bindings; - GLenum primitive{}; + ProgramVariant variant; bool operator==(const ShaderDiskCacheUsage& rhs) const { - return std::tie(unique_identifier, bindings, primitive) == - std::tie(rhs.unique_identifier, rhs.bindings, rhs.primitive); + return std::tie(unique_identifier, variant) == std::tie(rhs.unique_identifier, rhs.variant); } bool operator!=(const ShaderDiskCacheUsage& rhs) const { @@ -76,7 +95,19 @@ namespace std { template <> struct hash { std::size_t operator()(const OpenGL::BaseBindings& bindings) const noexcept { - return bindings.cbuf | bindings.gmem << 8 | bindings.sampler << 16; + return static_cast(bindings.cbuf) ^ + (static_cast(bindings.gmem) << 8) ^ + (static_cast(bindings.sampler) << 16) ^ + (static_cast(bindings.image) << 24); + } +}; + +template <> +struct hash { + std::size_t operator()(const OpenGL::ProgramVariant& variant) const noexcept { + return std::hash()(variant.base_bindings) ^ + std::hash()(variant.texture_buffer_usage) ^ + (static_cast(variant.primitive_mode) << 6); } }; @@ -84,7 +115,7 @@ template <> struct hash { std::size_t operator()(const OpenGL::ShaderDiskCacheUsage& usage) const noexcept { return static_cast(usage.unique_identifier) ^ - std::hash()(usage.bindings) ^ usage.primitive << 16; + std::hash()(usage.variant); } }; @@ -275,26 +306,17 @@ private: return LoadArrayFromPrecompiled(&object, 1); } - bool LoadObjectFromPrecompiled(bool& object) { - u8 value; - const bool read_ok = LoadArrayFromPrecompiled(&value, 1); - if (!read_ok) { - return false; - } - - object = value != 0; - return true; - } - - // Core system Core::System& system; - // Stored transferable shaders - std::map> transferable; - // Stores whole precompiled cache which will be read from/saved to the precompiled cache file + + // Stores whole precompiled cache which will be read from or saved to the precompiled chache + // file FileSys::VectorVfsFile precompiled_cache_virtual_file; // Stores the current offset of the precompiled cache file for IO purposes std::size_t precompiled_cache_virtual_file_offset = 0; + // Stored transferable shaders + std::unordered_map> transferable; + // The cache has been loaded at boot bool tried_to_load{}; }; diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.cpp b/src/video_core/renderer_opengl/gl_stream_buffer.cpp index d0b14b3f6d..35ba334e41 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.cpp +++ b/src/video_core/renderer_opengl/gl_stream_buffer.cpp @@ -15,7 +15,8 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning", namespace OpenGL { -OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent) +OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent, + bool use_persistent) : buffer_size(size) { gl_buffer.Create(); @@ -29,7 +30,7 @@ OGLStreamBuffer::OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool p allocate_size *= 2; } - if (GLAD_GL_ARB_buffer_storage) { + if (use_persistent) { persistent = true; coherent = prefer_coherent; const GLbitfield flags = diff --git a/src/video_core/renderer_opengl/gl_stream_buffer.h b/src/video_core/renderer_opengl/gl_stream_buffer.h index 3d18ecb4da..f8383cbd45 100644 --- a/src/video_core/renderer_opengl/gl_stream_buffer.h +++ b/src/video_core/renderer_opengl/gl_stream_buffer.h @@ -13,7 +13,8 @@ namespace OpenGL { class OGLStreamBuffer : private NonCopyable { public: - explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false); + explicit OGLStreamBuffer(GLsizeiptr size, bool vertex_data_usage, bool prefer_coherent = false, + bool use_persistent = true); ~OGLStreamBuffer(); GLuint GetHandle() const; diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp new file mode 100644 index 0000000000..08ae1a429b --- /dev/null +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -0,0 +1,614 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/bit_util.h" +#include "common/common_types.h" +#include "common/microprofile.h" +#include "common/scope_exit.h" +#include "core/core.h" +#include "video_core/morton.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/gl_texture_cache.h" +#include "video_core/renderer_opengl/utils.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/texture_cache.h" +#include "video_core/textures/convert.h" +#include "video_core/textures/texture.h" + +namespace OpenGL { + +using Tegra::Texture::SwizzleSource; +using VideoCore::MortonSwizzleMode; + +using VideoCore::Surface::ComponentType; +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::SurfaceCompression; +using VideoCore::Surface::SurfaceTarget; +using VideoCore::Surface::SurfaceType; + +MICROPROFILE_DEFINE(OpenGL_Texture_Upload, "OpenGL", "Texture Upload", MP_RGB(128, 192, 128)); +MICROPROFILE_DEFINE(OpenGL_Texture_Download, "OpenGL", "Texture Download", MP_RGB(128, 192, 128)); + +namespace { + +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; + ComponentType component_type; + bool compressed; +}; + +constexpr std::array tex_format_tuples = {{ + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U + {GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S + {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI + {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV, ComponentType::UNorm, false}, // B5G6R5U + {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, ComponentType::UNorm, + false}, // A2B10G10R10U + {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV, ComponentType::UNorm, false}, // A1B5G5R5U + {GL_R8, GL_RED, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // R8U + {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // R8UI + {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, ComponentType::Float, false}, // RGBA16F + {GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RGBA16U + {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RGBA16UI + {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float, + false}, // R11FG11FB10F + {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI + {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT1 + {GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT23 + {GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT45 + {GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, true}, // DXN1 + {GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXN2UNORM + {GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_INT, ComponentType::SNorm, true}, // DXN2SNORM + {GL_COMPRESSED_RGBA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // BC7U + {GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, + true}, // BC6H_UF16 + {GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::Float, + true}, // BC6H_SF16 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4 + {GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 + {GL_RGBA32F, GL_RGBA, GL_FLOAT, ComponentType::Float, false}, // RGBA32F + {GL_RG32F, GL_RG, GL_FLOAT, ComponentType::Float, false}, // RG32F + {GL_R32F, GL_RED, GL_FLOAT, ComponentType::Float, false}, // R32F + {GL_R16F, GL_RED, GL_HALF_FLOAT, ComponentType::Float, false}, // R16F + {GL_R16, GL_RED, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // R16U + {GL_R16_SNORM, GL_RED, GL_SHORT, ComponentType::SNorm, false}, // R16S + {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // R16UI + {GL_R16I, GL_RED_INTEGER, GL_SHORT, ComponentType::SInt, false}, // R16I + {GL_RG16, GL_RG, GL_UNSIGNED_SHORT, ComponentType::UNorm, false}, // RG16 + {GL_RG16F, GL_RG, GL_HALF_FLOAT, ComponentType::Float, false}, // RG16F + {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT, ComponentType::UInt, false}, // RG16UI + {GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I + {GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S + {GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, + false}, // RGBA8_SRGB + {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U + {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S + {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI + {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5 + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4 + {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8 + // Compressed sRGB formats + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT1_SRGB + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT23_SRGB + {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // DXT45_SRGB + {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm, + true}, // BC7U_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5 + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB + {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8 + {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_10X8_SRGB + + // Depth formats + {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F + {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, ComponentType::UNorm, + false}, // Z16 + + // DepthStencil formats + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, + false}, // Z24S8 + {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, ComponentType::UNorm, + false}, // S8Z24 + {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT_32_UNSIGNED_INT_24_8_REV, + ComponentType::Float, false}, // Z32FS8 +}}; + +const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType component_type) { + ASSERT(static_cast(pixel_format) < tex_format_tuples.size()); + const auto& format{tex_format_tuples[static_cast(pixel_format)]}; + ASSERT(component_type == format.component_type); + return format; +} + +GLenum GetTextureTarget(const SurfaceTarget& target) { + switch (target) { + case SurfaceTarget::TextureBuffer: + return GL_TEXTURE_BUFFER; + case SurfaceTarget::Texture1D: + return GL_TEXTURE_1D; + case SurfaceTarget::Texture2D: + return GL_TEXTURE_2D; + case SurfaceTarget::Texture3D: + return GL_TEXTURE_3D; + case SurfaceTarget::Texture1DArray: + return GL_TEXTURE_1D_ARRAY; + case SurfaceTarget::Texture2DArray: + return GL_TEXTURE_2D_ARRAY; + case SurfaceTarget::TextureCubemap: + return GL_TEXTURE_CUBE_MAP; + case SurfaceTarget::TextureCubeArray: + return GL_TEXTURE_CUBE_MAP_ARRAY; + } + UNREACHABLE(); + return {}; +} + +GLint GetSwizzleSource(SwizzleSource source) { + switch (source) { + case SwizzleSource::Zero: + return GL_ZERO; + case SwizzleSource::R: + return GL_RED; + case SwizzleSource::G: + return GL_GREEN; + case SwizzleSource::B: + return GL_BLUE; + case SwizzleSource::A: + return GL_ALPHA; + case SwizzleSource::OneInt: + case SwizzleSource::OneFloat: + return GL_ONE; + } + UNREACHABLE(); + return GL_NONE; +} + +void ApplyTextureDefaults(const SurfaceParams& params, GLuint texture) { + glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTextureParameteri(texture, GL_TEXTURE_MAX_LEVEL, params.num_levels - 1); + if (params.num_levels == 1) { + glTextureParameterf(texture, GL_TEXTURE_LOD_BIAS, 1000.0f); + } +} + +OGLTexture CreateTexture(const SurfaceParams& params, GLenum target, GLenum internal_format, + OGLBuffer& texture_buffer) { + OGLTexture texture; + texture.Create(target); + + switch (params.target) { + case SurfaceTarget::Texture1D: + glTextureStorage1D(texture.handle, params.emulated_levels, internal_format, params.width); + break; + case SurfaceTarget::TextureBuffer: + texture_buffer.Create(); + glNamedBufferStorage(texture_buffer.handle, params.width * params.GetBytesPerPixel(), + nullptr, GL_DYNAMIC_STORAGE_BIT); + glTextureBuffer(texture.handle, internal_format, texture_buffer.handle); + case SurfaceTarget::Texture2D: + case SurfaceTarget::TextureCubemap: + glTextureStorage2D(texture.handle, params.emulated_levels, internal_format, params.width, + params.height); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glTextureStorage3D(texture.handle, params.emulated_levels, internal_format, params.width, + params.height, params.depth); + break; + default: + UNREACHABLE(); + } + + ApplyTextureDefaults(params, texture.handle); + + return texture; +} + +} // Anonymous namespace + +CachedSurface::CachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) + : VideoCommon::SurfaceBase(gpu_addr, params) { + const auto& tuple{GetFormatTuple(params.pixel_format, params.component_type)}; + internal_format = tuple.internal_format; + format = tuple.format; + type = tuple.type; + is_compressed = tuple.compressed; + target = GetTextureTarget(params.target); + texture = CreateTexture(params, target, internal_format, texture_buffer); + DecorateSurfaceName(); + main_view = CreateViewInner( + ViewParams(params.target, 0, params.is_layered ? params.depth : 1, 0, params.num_levels), + true); +} + +CachedSurface::~CachedSurface() = default; + +void CachedSurface::DownloadTexture(std::vector& staging_buffer) { + MICROPROFILE_SCOPE(OpenGL_Texture_Download); + + SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); }); + + for (u32 level = 0; level < params.emulated_levels; ++level) { + glPixelStorei(GL_PACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level))); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast(params.GetMipWidth(level))); + const std::size_t mip_offset = params.GetHostMipmapLevelOffset(level); + if (is_compressed) { + glGetCompressedTextureImage(texture.handle, level, + static_cast(params.GetHostMipmapSize(level)), + staging_buffer.data() + mip_offset); + } else { + glGetTextureImage(texture.handle, level, format, type, + static_cast(params.GetHostMipmapSize(level)), + staging_buffer.data() + mip_offset); + } + } +} + +void CachedSurface::UploadTexture(const std::vector& staging_buffer) { + MICROPROFILE_SCOPE(OpenGL_Texture_Upload); + SCOPE_EXIT({ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); }); + for (u32 level = 0; level < params.emulated_levels; ++level) { + UploadTextureMipmap(level, staging_buffer); + } +} + +void CachedSurface::UploadTextureMipmap(u32 level, const std::vector& staging_buffer) { + glPixelStorei(GL_UNPACK_ALIGNMENT, std::min(8U, params.GetRowAlignment(level))); + glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast(params.GetMipWidth(level))); + + auto compression_type = params.GetCompressionType(); + + const std::size_t mip_offset = compression_type == SurfaceCompression::Converted + ? params.GetConvertedMipmapOffset(level) + : params.GetHostMipmapLevelOffset(level); + const u8* buffer{staging_buffer.data() + mip_offset}; + if (is_compressed) { + const auto image_size{static_cast(params.GetHostMipmapSize(level))}; + switch (params.target) { + case SurfaceTarget::Texture2D: + glCompressedTextureSubImage2D(texture.handle, level, 0, 0, + static_cast(params.GetMipWidth(level)), + static_cast(params.GetMipHeight(level)), + internal_format, image_size, buffer); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glCompressedTextureSubImage3D(texture.handle, level, 0, 0, 0, + static_cast(params.GetMipWidth(level)), + static_cast(params.GetMipHeight(level)), + static_cast(params.GetMipDepth(level)), + internal_format, image_size, buffer); + break; + case SurfaceTarget::TextureCubemap: { + const std::size_t layer_size{params.GetHostLayerSize(level)}; + for (std::size_t face = 0; face < params.depth; ++face) { + glCompressedTextureSubImage3D(texture.handle, level, 0, 0, static_cast(face), + static_cast(params.GetMipWidth(level)), + static_cast(params.GetMipHeight(level)), 1, + internal_format, static_cast(layer_size), + buffer); + buffer += layer_size; + } + break; + } + default: + UNREACHABLE(); + } + } else { + switch (params.target) { + case SurfaceTarget::Texture1D: + glTextureSubImage1D(texture.handle, level, 0, params.GetMipWidth(level), format, type, + buffer); + break; + case SurfaceTarget::TextureBuffer: + ASSERT(level == 0); + glNamedBufferSubData(texture_buffer.handle, 0, + params.GetMipWidth(level) * params.GetBytesPerPixel(), buffer); + break; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2D: + glTextureSubImage2D(texture.handle, level, 0, 0, params.GetMipWidth(level), + params.GetMipHeight(level), format, type, buffer); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glTextureSubImage3D( + texture.handle, level, 0, 0, 0, static_cast(params.GetMipWidth(level)), + static_cast(params.GetMipHeight(level)), + static_cast(params.GetMipDepth(level)), format, type, buffer); + break; + case SurfaceTarget::TextureCubemap: + for (std::size_t face = 0; face < params.depth; ++face) { + glTextureSubImage3D(texture.handle, level, 0, 0, static_cast(face), + params.GetMipWidth(level), params.GetMipHeight(level), 1, + format, type, buffer); + buffer += params.GetHostLayerSize(level); + } + break; + default: + UNREACHABLE(); + } + } +} + +void CachedSurface::DecorateSurfaceName() { + LabelGLObject(GL_TEXTURE, texture.handle, GetGpuAddr(), params.TargetName()); +} + +void CachedSurfaceView::DecorateViewName(GPUVAddr gpu_addr, std::string prefix) { + LabelGLObject(GL_TEXTURE, texture_view.handle, gpu_addr, prefix); +} + +View CachedSurface::CreateView(const ViewParams& view_key) { + return CreateViewInner(view_key, false); +} + +View CachedSurface::CreateViewInner(const ViewParams& view_key, const bool is_proxy) { + auto view = std::make_shared(*this, view_key, is_proxy); + views[view_key] = view; + if (!is_proxy) + view->DecorateViewName(gpu_addr, params.TargetName() + "V:" + std::to_string(view_count++)); + return view; +} + +CachedSurfaceView::CachedSurfaceView(CachedSurface& surface, const ViewParams& params, + const bool is_proxy) + : VideoCommon::ViewBase(params), surface{surface}, is_proxy{is_proxy} { + target = GetTextureTarget(params.target); + if (!is_proxy) { + texture_view = CreateTextureView(); + } + swizzle = EncodeSwizzle(SwizzleSource::R, SwizzleSource::G, SwizzleSource::B, SwizzleSource::A); +} + +CachedSurfaceView::~CachedSurfaceView() = default; + +void CachedSurfaceView::Attach(GLenum attachment, GLenum target) const { + ASSERT(params.num_layers == 1 && params.num_levels == 1); + + const auto& owner_params = surface.GetSurfaceParams(); + + switch (owner_params.target) { + case SurfaceTarget::Texture1D: + glFramebufferTexture1D(target, attachment, surface.GetTarget(), surface.GetTexture(), + params.base_level); + break; + case SurfaceTarget::Texture2D: + glFramebufferTexture2D(target, attachment, surface.GetTarget(), surface.GetTexture(), + params.base_level); + break; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + glFramebufferTextureLayer(target, attachment, surface.GetTexture(), params.base_level, + params.base_layer); + break; + default: + UNIMPLEMENTED(); + } +} + +void CachedSurfaceView::ApplySwizzle(SwizzleSource x_source, SwizzleSource y_source, + SwizzleSource z_source, SwizzleSource w_source) { + u32 new_swizzle = EncodeSwizzle(x_source, y_source, z_source, w_source); + if (new_swizzle == swizzle) + return; + swizzle = new_swizzle; + const std::array gl_swizzle = {GetSwizzleSource(x_source), GetSwizzleSource(y_source), + GetSwizzleSource(z_source), + GetSwizzleSource(w_source)}; + const GLuint handle = GetTexture(); + glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data()); +} + +OGLTextureView CachedSurfaceView::CreateTextureView() const { + const auto& owner_params = surface.GetSurfaceParams(); + OGLTextureView texture_view; + texture_view.Create(); + + const GLuint handle{texture_view.handle}; + const FormatTuple& tuple{ + GetFormatTuple(owner_params.pixel_format, owner_params.component_type)}; + + glTextureView(handle, target, surface.texture.handle, tuple.internal_format, params.base_level, + params.num_levels, params.base_layer, params.num_layers); + + ApplyTextureDefaults(owner_params, handle); + + return texture_view; +} + +TextureCacheOpenGL::TextureCacheOpenGL(Core::System& system, + VideoCore::RasterizerInterface& rasterizer, + const Device& device) + : TextureCacheBase{system, rasterizer} { + src_framebuffer.Create(); + dst_framebuffer.Create(); +} + +TextureCacheOpenGL::~TextureCacheOpenGL() = default; + +Surface TextureCacheOpenGL::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) { + return std::make_shared(gpu_addr, params); +} + +void TextureCacheOpenGL::ImageCopy(Surface& src_surface, Surface& dst_surface, + const VideoCommon::CopyParams& copy_params) { + const auto& src_params = src_surface->GetSurfaceParams(); + const auto& dst_params = dst_surface->GetSurfaceParams(); + if (src_params.type != dst_params.type) { + // A fallback is needed + return; + } + const auto src_handle = src_surface->GetTexture(); + const auto src_target = src_surface->GetTarget(); + const auto dst_handle = dst_surface->GetTexture(); + const auto dst_target = dst_surface->GetTarget(); + glCopyImageSubData(src_handle, src_target, copy_params.source_level, copy_params.source_x, + copy_params.source_y, copy_params.source_z, dst_handle, dst_target, + copy_params.dest_level, copy_params.dest_x, copy_params.dest_y, + copy_params.dest_z, copy_params.width, copy_params.height, + copy_params.depth); +} + +void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) { + const auto& src_params{src_view->GetSurfaceParams()}; + const auto& dst_params{dst_view->GetSurfaceParams()}; + + OpenGLState prev_state{OpenGLState::GetCurState()}; + SCOPE_EXIT({ prev_state.Apply(); }); + + OpenGLState state; + state.draw.read_framebuffer = src_framebuffer.handle; + state.draw.draw_framebuffer = dst_framebuffer.handle; + state.Apply(); + + u32 buffers{}; + + UNIMPLEMENTED_IF(src_params.target == SurfaceTarget::Texture3D); + UNIMPLEMENTED_IF(dst_params.target == SurfaceTarget::Texture3D); + + if (src_params.type == SurfaceType::ColorTexture) { + src_view->Attach(GL_COLOR_ATTACHMENT0, GL_READ_FRAMEBUFFER); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + dst_view->Attach(GL_COLOR_ATTACHMENT0, GL_DRAW_FRAMEBUFFER); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + buffers = GL_COLOR_BUFFER_BIT; + } else if (src_params.type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + src_view->Attach(GL_DEPTH_ATTACHMENT, GL_READ_FRAMEBUFFER); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + dst_view->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_DEPTH_BUFFER_BIT; + } else if (src_params.type == SurfaceType::DepthStencil) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + src_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_READ_FRAMEBUFFER); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + dst_view->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER); + + buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } + + const Common::Rectangle& src_rect = copy_config.src_rect; + const Common::Rectangle& dst_rect = copy_config.dst_rect; + const bool is_linear = copy_config.filter == Tegra::Engines::Fermi2D::Filter::Linear; + + glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left, + dst_rect.top, dst_rect.right, dst_rect.bottom, buffers, + is_linear && (buffers == GL_COLOR_BUFFER_BIT) ? GL_LINEAR : GL_NEAREST); +} + +void TextureCacheOpenGL::BufferCopy(Surface& src_surface, Surface& dst_surface) { + const auto& src_params = src_surface->GetSurfaceParams(); + const auto& dst_params = dst_surface->GetSurfaceParams(); + UNIMPLEMENTED_IF(src_params.num_levels > 1 || dst_params.num_levels > 1); + + const auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type); + const auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type); + + const std::size_t source_size = src_surface->GetHostSizeInBytes(); + const std::size_t dest_size = dst_surface->GetHostSizeInBytes(); + + const std::size_t buffer_size = std::max(source_size, dest_size); + + GLuint copy_pbo_handle = FetchPBO(buffer_size); + + glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle); + + if (source_format.compressed) { + glGetCompressedTextureImage(src_surface->GetTexture(), 0, static_cast(source_size), + nullptr); + } else { + glGetTextureImage(src_surface->GetTexture(), 0, source_format.format, source_format.type, + static_cast(source_size), nullptr); + } + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, copy_pbo_handle); + + const GLsizei width = static_cast(dst_params.width); + const GLsizei height = static_cast(dst_params.height); + const GLsizei depth = static_cast(dst_params.depth); + if (dest_format.compressed) { + LOG_CRITICAL(HW_GPU, "Compressed buffer copy is unimplemented!"); + UNREACHABLE(); + } else { + switch (dst_params.target) { + case SurfaceTarget::Texture1D: + glTextureSubImage1D(dst_surface->GetTexture(), 0, 0, width, dest_format.format, + dest_format.type, nullptr); + break; + case SurfaceTarget::Texture2D: + glTextureSubImage2D(dst_surface->GetTexture(), 0, 0, 0, width, height, + dest_format.format, dest_format.type, nullptr); + break; + case SurfaceTarget::Texture3D: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth, + dest_format.format, dest_format.type, nullptr); + break; + case SurfaceTarget::TextureCubemap: + glTextureSubImage3D(dst_surface->GetTexture(), 0, 0, 0, 0, width, height, depth, + dest_format.format, dest_format.type, nullptr); + break; + default: + LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", + static_cast(dst_params.target)); + UNREACHABLE(); + } + } + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + glTextureBarrier(); +} + +GLuint TextureCacheOpenGL::FetchPBO(std::size_t buffer_size) { + ASSERT_OR_EXECUTE(buffer_size > 0, { return 0; }); + const u32 l2 = Common::Log2Ceil64(static_cast(buffer_size)); + OGLBuffer& cp = copy_pbo_cache[l2]; + if (cp.handle == 0) { + const std::size_t ceil_size = 1ULL << l2; + cp.Create(); + cp.MakeStreamCopy(ceil_size); + } + return cp.handle; +} + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h new file mode 100644 index 0000000000..ff6ab69881 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -0,0 +1,143 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "common/common_types.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/renderer_opengl/gl_device.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" +#include "video_core/texture_cache/texture_cache.h" + +namespace OpenGL { + +using VideoCommon::SurfaceParams; +using VideoCommon::ViewParams; + +class CachedSurfaceView; +class CachedSurface; +class TextureCacheOpenGL; + +using Surface = std::shared_ptr; +using View = std::shared_ptr; +using TextureCacheBase = VideoCommon::TextureCache; + +class CachedSurface final : public VideoCommon::SurfaceBase { + friend CachedSurfaceView; + +public: + explicit CachedSurface(GPUVAddr gpu_addr, const SurfaceParams& params); + ~CachedSurface(); + + void UploadTexture(const std::vector& staging_buffer) override; + void DownloadTexture(std::vector& staging_buffer) override; + + GLenum GetTarget() const { + return target; + } + + GLuint GetTexture() const { + return texture.handle; + } + +protected: + void DecorateSurfaceName(); + + View CreateView(const ViewParams& view_key) override; + View CreateViewInner(const ViewParams& view_key, bool is_proxy); + +private: + void UploadTextureMipmap(u32 level, const std::vector& staging_buffer); + + GLenum internal_format{}; + GLenum format{}; + GLenum type{}; + bool is_compressed{}; + GLenum target{}; + u32 view_count{}; + + OGLTexture texture; + OGLBuffer texture_buffer; +}; + +class CachedSurfaceView final : public VideoCommon::ViewBase { +public: + explicit CachedSurfaceView(CachedSurface& surface, const ViewParams& params, bool is_proxy); + ~CachedSurfaceView(); + + /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER + void Attach(GLenum attachment, GLenum target) const; + + GLuint GetTexture() const { + if (is_proxy) { + return surface.GetTexture(); + } + return texture_view.handle; + } + + const SurfaceParams& GetSurfaceParams() const { + return surface.GetSurfaceParams(); + } + + void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source); + + void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); + +private: + u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source) const { + return (static_cast(x_source) << 24) | (static_cast(y_source) << 16) | + (static_cast(z_source) << 8) | static_cast(w_source); + } + + OGLTextureView CreateTextureView() const; + + CachedSurface& surface; + GLenum target{}; + + OGLTextureView texture_view; + u32 swizzle; + bool is_proxy; +}; + +class TextureCacheOpenGL final : public TextureCacheBase { +public: + explicit TextureCacheOpenGL(Core::System& system, VideoCore::RasterizerInterface& rasterizer, + const Device& device); + ~TextureCacheOpenGL(); + +protected: + Surface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) override; + + void ImageCopy(Surface& src_surface, Surface& dst_surface, + const VideoCommon::CopyParams& copy_params) override; + + void ImageBlit(View& src_view, View& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) override; + + void BufferCopy(Surface& src_surface, Surface& dst_surface) override; + +private: + GLuint FetchPBO(std::size_t buffer_size); + + OGLFramebuffer src_framebuffer; + OGLFramebuffer dst_framebuffer; + std::unordered_map copy_pbo_cache; +}; + +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index aafd6f31b5..b142521ecc 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -471,7 +471,6 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum } } -/// Initialize the renderer bool RendererOpenGL::Init() { Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window}; diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp index f23fc9f9d0..68c36988dd 100644 --- a/src/video_core/renderer_opengl/utils.cpp +++ b/src/video_core/renderer_opengl/utils.cpp @@ -5,8 +5,10 @@ #include #include #include + #include "common/assert.h" #include "common/common_types.h" +#include "common/scope_exit.h" #include "video_core/renderer_opengl/utils.h" namespace OpenGL { @@ -63,4 +65,4 @@ void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_vie glObjectLabel(identifier, handle, -1, static_cast(object_label.c_str())); } -} // namespace OpenGL \ No newline at end of file +} // namespace OpenGL diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h index b3e9fc499a..4a752f3b49 100644 --- a/src/video_core/renderer_opengl/utils.h +++ b/src/video_core/renderer_opengl/utils.h @@ -32,4 +32,4 @@ private: void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string_view extra_info = {}); -} // namespace OpenGL \ No newline at end of file +} // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index 33ad9764a9..97ce214b18 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -935,6 +935,11 @@ private: return {}; } + Id ImageStore(Operation operation) { + UNIMPLEMENTED(); + return {}; + } + Id Branch(Operation operation) { const auto target = std::get_if(&*operation[0]); UNIMPLEMENTED_IF(!target); @@ -1326,6 +1331,8 @@ private: &SPIRVDecompiler::TextureQueryLod, &SPIRVDecompiler::TexelFetch, + &SPIRVDecompiler::ImageStore, + &SPIRVDecompiler::Branch, &SPIRVDecompiler::PushFlowStack, &SPIRVDecompiler::PopFlowStack, diff --git a/src/video_core/shader/decode.cpp b/src/video_core/shader/decode.cpp index a0554c97e6..2c9ff28f2b 100644 --- a/src/video_core/shader/decode.cpp +++ b/src/video_core/shader/decode.cpp @@ -169,6 +169,7 @@ u32 ShaderIR::DecodeInstr(NodeBlock& bb, u32 pc) { {OpCode::Type::Conversion, &ShaderIR::DecodeConversion}, {OpCode::Type::Memory, &ShaderIR::DecodeMemory}, {OpCode::Type::Texture, &ShaderIR::DecodeTexture}, + {OpCode::Type::Image, &ShaderIR::DecodeImage}, {OpCode::Type::FloatSetPredicate, &ShaderIR::DecodeFloatSetPredicate}, {OpCode::Type::IntegerSetPredicate, &ShaderIR::DecodeIntegerSetPredicate}, {OpCode::Type::HalfSetPredicate, &ShaderIR::DecodeHalfSetPredicate}, diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp new file mode 100644 index 0000000000..24f022cc04 --- /dev/null +++ b/src/video_core/shader/decode/image.cpp @@ -0,0 +1,120 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/shader/node_helper.h" +#include "video_core/shader/shader_ir.h" + +namespace VideoCommon::Shader { + +using Tegra::Shader::Instruction; +using Tegra::Shader::OpCode; + +namespace { +std::size_t GetImageTypeNumCoordinates(Tegra::Shader::ImageType image_type) { + switch (image_type) { + case Tegra::Shader::ImageType::Texture1D: + case Tegra::Shader::ImageType::TextureBuffer: + return 1; + case Tegra::Shader::ImageType::Texture1DArray: + case Tegra::Shader::ImageType::Texture2D: + return 2; + case Tegra::Shader::ImageType::Texture2DArray: + case Tegra::Shader::ImageType::Texture3D: + return 3; + } + UNREACHABLE(); + return 1; +} +} // Anonymous namespace + +u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { + const Instruction instr = {program_code[pc]}; + const auto opcode = OpCode::Decode(instr); + + switch (opcode->get().GetId()) { + case OpCode::Id::SUST: { + UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); + UNIMPLEMENTED_IF(instr.sust.image_type == Tegra::Shader::ImageType::TextureBuffer); + UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); + UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store + + std::vector values; + constexpr std::size_t hardcoded_size{4}; + for (std::size_t i = 0; i < hardcoded_size; ++i) { + values.push_back(GetRegister(instr.gpr0.Value() + i)); + } + + std::vector coords; + const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)}; + for (std::size_t i = 0; i < num_coords; ++i) { + coords.push_back(GetRegister(instr.gpr8.Value() + i)); + } + + const auto type{instr.sust.image_type}; + const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; + MetaImage meta{image, values}; + const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; + bb.push_back(store); + break; + } + default: + UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); + } + + return pc; +} + +const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { + const auto offset{static_cast(image.index.Value())}; + + // If this image has already been used, return the existing mapping. + const auto itr{std::find_if(used_images.begin(), used_images.end(), + [=](const Image& entry) { return entry.GetOffset() == offset; })}; + if (itr != used_images.end()) { + ASSERT(itr->GetType() == type); + return *itr; + } + + // Otherwise create a new mapping for this image. + const std::size_t next_index{used_images.size()}; + const Image entry{offset, next_index, type}; + return *used_images.emplace(entry).first; +} + +const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, + Tegra::Shader::ImageType type) { + const Node image_register{GetRegister(reg)}; + const Node base_image{ + TrackCbuf(image_register, global_code, static_cast(global_code.size()))}; + const auto cbuf{std::get_if(&*base_image)}; + const auto cbuf_offset_imm{std::get_if(&*cbuf->GetOffset())}; + const auto cbuf_offset{cbuf_offset_imm->GetValue()}; + const auto cbuf_index{cbuf->GetIndex()}; + const auto cbuf_key{(static_cast(cbuf_index) << 32) | static_cast(cbuf_offset)}; + + // If this image has already been used, return the existing mapping. + const auto itr{std::find_if(used_images.begin(), used_images.end(), + [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })}; + if (itr != used_images.end()) { + ASSERT(itr->GetType() == type); + return *itr; + } + + // Otherwise create a new mapping for this image. + const std::size_t next_index{used_images.size()}; + const Image entry{cbuf_index, cbuf_offset, next_index, type}; + return *used_images.emplace(entry).first; +} + +} // namespace VideoCommon::Shader diff --git a/src/video_core/shader/decode/texture.cpp b/src/video_core/shader/decode/texture.cpp index 4a356dbd4d..cb480be9bd 100644 --- a/src/video_core/shader/decode/texture.cpp +++ b/src/video_core/shader/decode/texture.cpp @@ -245,6 +245,18 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) { } break; } + case OpCode::Id::TLD: { + UNIMPLEMENTED_IF_MSG(instr.tld.aoffi, "AOFFI is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld.ms, "MS is not implemented"); + UNIMPLEMENTED_IF_MSG(instr.tld.cl, "CL is not implemented"); + + if (instr.tld.nodep_flag) { + LOG_WARNING(HW_GPU, "TLD.NODEP implementation is incomplete"); + } + + WriteTexInstructionFloat(bb, instr, GetTldCode(instr)); + break; + } case OpCode::Id::TLDS: { const Tegra::Shader::TextureType texture_type{instr.tlds.GetTextureType()}; const bool is_array{instr.tlds.IsArrayTexture()}; @@ -575,6 +587,39 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de return values; } +Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) { + const auto texture_type{instr.tld.texture_type}; + const bool is_array{instr.tld.is_array}; + const bool lod_enabled{instr.tld.GetTextureProcessMode() == TextureProcessMode::LL}; + const std::size_t coord_count{GetCoordCount(texture_type)}; + + u64 gpr8_cursor{instr.gpr8.Value()}; + const Node array_register{is_array ? GetRegister(gpr8_cursor++) : nullptr}; + + std::vector coords; + coords.reserve(coord_count); + for (std::size_t i = 0; i < coord_count; ++i) { + coords.push_back(GetRegister(gpr8_cursor++)); + } + + u64 gpr20_cursor{instr.gpr20.Value()}; + // const Node bindless_register{is_bindless ? GetRegister(gpr20_cursor++) : nullptr}; + const Node lod{lod_enabled ? GetRegister(gpr20_cursor++) : Immediate(0u)}; + // const Node aoffi_register{is_aoffi ? GetRegister(gpr20_cursor++) : nullptr}; + // const Node multisample{is_multisample ? GetRegister(gpr20_cursor++) : nullptr}; + + const auto& sampler = GetSampler(instr.sampler, texture_type, is_array, false); + + Node4 values; + for (u32 element = 0; element < values.size(); ++element) { + auto coords_copy = coords; + MetaTexture meta{sampler, array_register, {}, {}, {}, lod, {}, element}; + values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy)); + } + + return values; +} + Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is_array) { const std::size_t type_coord_count = GetCoordCount(texture_type); const bool lod_enabled = instr.tlds.GetTextureProcessMode() == TextureProcessMode::LL; diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 3cfb911bbc..0ac83fcf08 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -146,6 +146,8 @@ enum class OperationCode { TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 TexelFetch, /// (MetaTexture, int[N], int) -> float4 + ImageStore, /// (MetaImage, float[N] coords) -> void + Branch, /// (uint branch_target) -> void PushFlowStack, /// (uint branch_target) -> void PopFlowStack, /// () -> void @@ -263,6 +265,48 @@ private: bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. }; +class Image { +public: + explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) + : offset{offset}, index{index}, type{type}, is_bindless{false} {} + + explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, + Tegra::Shader::ImageType type) + : offset{(static_cast(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, + is_bindless{true} {} + + explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, + bool is_bindless) + : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} + + std::size_t GetOffset() const { + return offset; + } + + std::size_t GetIndex() const { + return index; + } + + Tegra::Shader::ImageType GetType() const { + return type; + } + + bool IsBindless() const { + return is_bindless; + } + + bool operator<(const Image& rhs) const { + return std::tie(offset, index, type, is_bindless) < + std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); + } + +private: + std::size_t offset{}; + std::size_t index{}; + Tegra::Shader::ImageType type{}; + bool is_bindless{}; +}; + struct GlobalMemoryBase { u32 cbuf_index{}; u32 cbuf_offset{}; @@ -289,8 +333,14 @@ struct MetaTexture { u32 element{}; }; +struct MetaImage { + const Image& image; + std::vector values; +}; + /// Parameters that modify an operation but are not part of any particular operand -using Meta = std::variant; +using Meta = + std::variant; /// Holds any kind of operation that can be done in the IR class OperationNode final { diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index edcf2288ee..e225482081 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -104,6 +104,10 @@ public: return used_samplers; } + const std::set& GetImages() const { + return used_images; + } + const std::array& GetClipDistances() const { return used_clip_distances; @@ -154,6 +158,7 @@ private: u32 DecodeConversion(NodeBlock& bb, u32 pc); u32 DecodeMemory(NodeBlock& bb, u32 pc); u32 DecodeTexture(NodeBlock& bb, u32 pc); + u32 DecodeImage(NodeBlock& bb, u32 pc); u32 DecodeFloatSetPredicate(NodeBlock& bb, u32 pc); u32 DecodeIntegerSetPredicate(NodeBlock& bb, u32 pc); u32 DecodeHalfSetPredicate(NodeBlock& bb, u32 pc); @@ -254,6 +259,12 @@ private: Tegra::Shader::TextureType type, bool is_array, bool is_shadow); + /// Accesses an image. + const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); + + /// Access a bindless image sampler. + const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); + /// Extracts a sequence of bits from a node Node BitfieldExtract(Node value, u32 offset, u32 bits); @@ -277,6 +288,8 @@ private: Node4 GetTld4Code(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, bool depth_compare, bool is_array, bool is_aoffi); + Node4 GetTldCode(Tegra::Shader::Instruction instr); + Node4 GetTldsCode(Tegra::Shader::Instruction instr, Tegra::Shader::TextureType texture_type, bool is_array); @@ -327,6 +340,7 @@ private: std::set used_output_attributes; std::map used_cbufs; std::set used_samplers; + std::set used_images; std::array used_clip_distances{}; std::map used_global_memory; bool uses_physical_attributes{}; // Shader uses AL2P or physical attribute read/writes diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 6384fa8d2c..c50f6354d0 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -12,6 +12,8 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t switch (texture_type) { case Tegra::Texture::TextureType::Texture1D: return SurfaceTarget::Texture1D; + case Tegra::Texture::TextureType::Texture1DBuffer: + return SurfaceTarget::TextureBuffer; case Tegra::Texture::TextureType::Texture2D: case Tegra::Texture::TextureType::Texture2DNoMipmap: return SurfaceTarget::Texture2D; @@ -35,6 +37,7 @@ SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_t bool SurfaceTargetIsLayered(SurfaceTarget target) { switch (target) { case SurfaceTarget::Texture1D: + case SurfaceTarget::TextureBuffer: case SurfaceTarget::Texture2D: case SurfaceTarget::Texture3D: return false; @@ -53,6 +56,7 @@ bool SurfaceTargetIsLayered(SurfaceTarget target) { bool SurfaceTargetIsArray(SurfaceTarget target) { switch (target) { case SurfaceTarget::Texture1D: + case SurfaceTarget::TextureBuffer: case SurfaceTarget::Texture2D: case SurfaceTarget::Texture3D: case SurfaceTarget::TextureCubemap: @@ -304,8 +308,8 @@ PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format, return PixelFormat::Z32F; case Tegra::Texture::TextureFormat::Z16: return PixelFormat::Z16; - case Tegra::Texture::TextureFormat::Z24S8: - return PixelFormat::Z24S8; + case Tegra::Texture::TextureFormat::S8Z24: + return PixelFormat::S8Z24; case Tegra::Texture::TextureFormat::ZF32_X24S8: return PixelFormat::Z32FS8; case Tegra::Texture::TextureFormat::DXT1: diff --git a/src/video_core/surface.h b/src/video_core/surface.h index b783e4b277..83f31c12cd 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -114,6 +114,7 @@ enum class SurfaceType { enum class SurfaceTarget { Texture1D, + TextureBuffer, Texture2D, Texture3D, Texture1DArray, @@ -122,71 +123,71 @@ enum class SurfaceTarget { TextureCubeArray, }; -constexpr std::array compression_factor_table = {{ - 1, // ABGR8U - 1, // ABGR8S - 1, // ABGR8UI - 1, // B5G6R5U - 1, // A2B10G10R10U - 1, // A1B5G5R5U - 1, // R8U - 1, // R8UI - 1, // RGBA16F - 1, // RGBA16U - 1, // RGBA16UI - 1, // R11FG11FB10F - 1, // RGBA32UI - 4, // DXT1 - 4, // DXT23 - 4, // DXT45 - 4, // DXN1 - 4, // DXN2UNORM - 4, // DXN2SNORM - 4, // BC7U - 4, // BC6H_UF16 - 4, // BC6H_SF16 - 4, // ASTC_2D_4X4 - 1, // BGRA8 - 1, // RGBA32F - 1, // RG32F - 1, // R32F - 1, // R16F - 1, // R16U - 1, // R16S - 1, // R16UI - 1, // R16I - 1, // RG16 - 1, // RG16F - 1, // RG16UI - 1, // RG16I - 1, // RG16S - 1, // RGB32F - 1, // RGBA8_SRGB - 1, // RG8U - 1, // RG8S - 1, // RG32UI - 1, // R32UI - 4, // ASTC_2D_8X8 - 4, // ASTC_2D_8X5 - 4, // ASTC_2D_5X4 - 1, // BGRA8_SRGB - 4, // DXT1_SRGB - 4, // DXT23_SRGB - 4, // DXT45_SRGB - 4, // BC7U_SRGB - 4, // ASTC_2D_4X4_SRGB - 4, // ASTC_2D_8X8_SRGB - 4, // ASTC_2D_8X5_SRGB - 4, // ASTC_2D_5X4_SRGB - 4, // ASTC_2D_5X5 - 4, // ASTC_2D_5X5_SRGB - 4, // ASTC_2D_10X8 - 4, // ASTC_2D_10X8_SRGB - 1, // Z32F - 1, // Z16 - 1, // Z24S8 - 1, // S8Z24 - 1, // Z32FS8 +constexpr std::array compression_factor_shift_table = {{ + 0, // ABGR8U + 0, // ABGR8S + 0, // ABGR8UI + 0, // B5G6R5U + 0, // A2B10G10R10U + 0, // A1B5G5R5U + 0, // R8U + 0, // R8UI + 0, // RGBA16F + 0, // RGBA16U + 0, // RGBA16UI + 0, // R11FG11FB10F + 0, // RGBA32UI + 2, // DXT1 + 2, // DXT23 + 2, // DXT45 + 2, // DXN1 + 2, // DXN2UNORM + 2, // DXN2SNORM + 2, // BC7U + 2, // BC6H_UF16 + 2, // BC6H_SF16 + 2, // ASTC_2D_4X4 + 0, // BGRA8 + 0, // RGBA32F + 0, // RG32F + 0, // R32F + 0, // R16F + 0, // R16U + 0, // R16S + 0, // R16UI + 0, // R16I + 0, // RG16 + 0, // RG16F + 0, // RG16UI + 0, // RG16I + 0, // RG16S + 0, // RGB32F + 0, // RGBA8_SRGB + 0, // RG8U + 0, // RG8S + 0, // RG32UI + 0, // R32UI + 2, // ASTC_2D_8X8 + 2, // ASTC_2D_8X5 + 2, // ASTC_2D_5X4 + 0, // BGRA8_SRGB + 2, // DXT1_SRGB + 2, // DXT23_SRGB + 2, // DXT45_SRGB + 2, // BC7U_SRGB + 2, // ASTC_2D_4X4_SRGB + 2, // ASTC_2D_8X8_SRGB + 2, // ASTC_2D_8X5_SRGB + 2, // ASTC_2D_5X4_SRGB + 2, // ASTC_2D_5X5 + 2, // ASTC_2D_5X5_SRGB + 2, // ASTC_2D_10X8 + 2, // ASTC_2D_10X8_SRGB + 0, // Z32F + 0, // Z16 + 0, // Z24S8 + 0, // S8Z24 + 0, // Z32FS8 }}; /** @@ -195,12 +196,14 @@ constexpr std::array compression_factor_table = {{ * compressed image. This is used for maintaining proper surface sizes for compressed * texture formats. */ -static constexpr u32 GetCompressionFactor(PixelFormat format) { - if (format == PixelFormat::Invalid) - return 0; +inline constexpr u32 GetCompressionFactorShift(PixelFormat format) { + DEBUG_ASSERT(format != PixelFormat::Invalid); + DEBUG_ASSERT(static_cast(format) < compression_factor_shift_table.size()); + return compression_factor_shift_table[static_cast(format)]; +} - ASSERT(static_cast(format) < compression_factor_table.size()); - return compression_factor_table[static_cast(format)]; +inline constexpr u32 GetCompressionFactor(PixelFormat format) { + return 1U << GetCompressionFactorShift(format); } constexpr std::array block_width_table = {{ @@ -436,6 +439,88 @@ static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) { return GetFormatBpp(pixel_format) / CHAR_BIT; } +enum class SurfaceCompression { + None, // Not compressed + Compressed, // Texture is compressed + Converted, // Texture is converted before upload or after download + Rearranged, // Texture is swizzled before upload or after download +}; + +constexpr std::array compression_type_table = {{ + SurfaceCompression::None, // ABGR8U + SurfaceCompression::None, // ABGR8S + SurfaceCompression::None, // ABGR8UI + SurfaceCompression::None, // B5G6R5U + SurfaceCompression::None, // A2B10G10R10U + SurfaceCompression::None, // A1B5G5R5U + SurfaceCompression::None, // R8U + SurfaceCompression::None, // R8UI + SurfaceCompression::None, // RGBA16F + SurfaceCompression::None, // RGBA16U + SurfaceCompression::None, // RGBA16UI + SurfaceCompression::None, // R11FG11FB10F + SurfaceCompression::None, // RGBA32UI + SurfaceCompression::Compressed, // DXT1 + SurfaceCompression::Compressed, // DXT23 + SurfaceCompression::Compressed, // DXT45 + SurfaceCompression::Compressed, // DXN1 + SurfaceCompression::Compressed, // DXN2UNORM + SurfaceCompression::Compressed, // DXN2SNORM + SurfaceCompression::Compressed, // BC7U + SurfaceCompression::Compressed, // BC6H_UF16 + SurfaceCompression::Compressed, // BC6H_SF16 + SurfaceCompression::Converted, // ASTC_2D_4X4 + SurfaceCompression::None, // BGRA8 + SurfaceCompression::None, // RGBA32F + SurfaceCompression::None, // RG32F + SurfaceCompression::None, // R32F + SurfaceCompression::None, // R16F + SurfaceCompression::None, // R16U + SurfaceCompression::None, // R16S + SurfaceCompression::None, // R16UI + SurfaceCompression::None, // R16I + SurfaceCompression::None, // RG16 + SurfaceCompression::None, // RG16F + SurfaceCompression::None, // RG16UI + SurfaceCompression::None, // RG16I + SurfaceCompression::None, // RG16S + SurfaceCompression::None, // RGB32F + SurfaceCompression::None, // RGBA8_SRGB + SurfaceCompression::None, // RG8U + SurfaceCompression::None, // RG8S + SurfaceCompression::None, // RG32UI + SurfaceCompression::None, // R32UI + SurfaceCompression::Converted, // ASTC_2D_8X8 + SurfaceCompression::Converted, // ASTC_2D_8X5 + SurfaceCompression::Converted, // ASTC_2D_5X4 + SurfaceCompression::None, // BGRA8_SRGB + SurfaceCompression::Compressed, // DXT1_SRGB + SurfaceCompression::Compressed, // DXT23_SRGB + SurfaceCompression::Compressed, // DXT45_SRGB + SurfaceCompression::Compressed, // BC7U_SRGB + SurfaceCompression::Converted, // ASTC_2D_4X4_SRGB + SurfaceCompression::Converted, // ASTC_2D_8X8_SRGB + SurfaceCompression::Converted, // ASTC_2D_8X5_SRGB + SurfaceCompression::Converted, // ASTC_2D_5X4_SRGB + SurfaceCompression::Converted, // ASTC_2D_5X5 + SurfaceCompression::Converted, // ASTC_2D_5X5_SRGB + SurfaceCompression::Converted, // ASTC_2D_10X8 + SurfaceCompression::Converted, // ASTC_2D_10X8_SRGB + SurfaceCompression::None, // Z32F + SurfaceCompression::None, // Z16 + SurfaceCompression::None, // Z24S8 + SurfaceCompression::Rearranged, // S8Z24 + SurfaceCompression::None, // Z32FS8 +}}; + +constexpr SurfaceCompression GetFormatCompressionType(PixelFormat format) { + if (format == PixelFormat::Invalid) { + return SurfaceCompression::None; + } + DEBUG_ASSERT(static_cast(format) < compression_type_table.size()); + return compression_type_table[static_cast(format)]; +} + SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type); bool SurfaceTargetIsLayered(SurfaceTarget target); diff --git a/src/video_core/texture_cache.cpp b/src/video_core/texture_cache.cpp deleted file mode 100644 index e96eba7cc3..0000000000 --- a/src/video_core/texture_cache.cpp +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/alignment.h" -#include "common/assert.h" -#include "common/cityhash.h" -#include "common/common_types.h" -#include "core/core.h" -#include "video_core/surface.h" -#include "video_core/texture_cache.h" -#include "video_core/textures/decoders.h" -#include "video_core/textures/texture.h" - -namespace VideoCommon { - -using VideoCore::Surface::SurfaceTarget; - -using VideoCore::Surface::ComponentTypeFromDepthFormat; -using VideoCore::Surface::ComponentTypeFromRenderTarget; -using VideoCore::Surface::ComponentTypeFromTexture; -using VideoCore::Surface::PixelFormatFromDepthFormat; -using VideoCore::Surface::PixelFormatFromRenderTargetFormat; -using VideoCore::Surface::PixelFormatFromTextureFormat; -using VideoCore::Surface::SurfaceTargetFromTextureType; - -constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { - return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); -} - -SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config) { - SurfaceParams params; - params.is_tiled = config.tic.IsTiled(); - params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, - params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, - params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, - params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; - params.pixel_format = - PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false); - params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); - params.type = GetFormatType(params.pixel_format); - params.target = SurfaceTargetFromTextureType(config.tic.texture_type); - params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); - params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); - params.depth = config.tic.Depth(); - if (params.target == SurfaceTarget::TextureCubemap || - params.target == SurfaceTarget::TextureCubeArray) { - params.depth *= 6; - } - params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); - params.unaligned_height = config.tic.Height(); - params.num_levels = config.tic.max_mip_level + 1; - - params.CalculateCachedValues(); - return params; -} - -SurfaceParams SurfaceParams::CreateForDepthBuffer( - Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { - SurfaceParams params; - params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << std::min(block_width, 5U); - params.block_height = 1 << std::min(block_height, 5U); - params.block_depth = 1 << std::min(block_depth, 5U); - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromDepthFormat(format); - params.component_type = ComponentTypeFromDepthFormat(format); - params.type = GetFormatType(params.pixel_format); - params.width = zeta_width; - params.height = zeta_height; - params.unaligned_height = zeta_height; - params.target = SurfaceTarget::Texture2D; - params.depth = 1; - params.num_levels = 1; - - params.CalculateCachedValues(); - return params; -} - -SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { - const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; - SurfaceParams params; - params.is_tiled = - config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; - params.block_width = 1 << config.memory_layout.block_width; - params.block_height = 1 << config.memory_layout.block_height; - params.block_depth = 1 << config.memory_layout.block_depth; - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - if (params.is_tiled) { - params.width = config.width; - } else { - const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; - params.pitch = config.width; - params.width = params.pitch / bpp; - } - params.height = config.height; - params.depth = 1; - params.unaligned_height = config.height; - params.target = SurfaceTarget::Texture2D; - params.num_levels = 1; - - params.CalculateCachedValues(); - return params; -} - -SurfaceParams SurfaceParams::CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - SurfaceParams params{}; - params.is_tiled = !config.linear; - params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, - params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, - params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, - params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); - params.component_type = ComponentTypeFromRenderTarget(config.format); - params.type = GetFormatType(params.pixel_format); - params.width = config.width; - params.height = config.height; - params.unaligned_height = config.height; - // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters - params.target = SurfaceTarget::Texture2D; - params.depth = 1; - params.num_levels = 1; - - params.CalculateCachedValues(); - return params; -} - -u32 SurfaceParams::GetMipWidth(u32 level) const { - return std::max(1U, width >> level); -} - -u32 SurfaceParams::GetMipHeight(u32 level) const { - return std::max(1U, height >> level); -} - -u32 SurfaceParams::GetMipDepth(u32 level) const { - return IsLayered() ? depth : std::max(1U, depth >> level); -} - -bool SurfaceParams::IsLayered() const { - switch (target) { - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubeArray: - case SurfaceTarget::TextureCubemap: - return true; - default: - return false; - } -} - -u32 SurfaceParams::GetMipBlockHeight(u32 level) const { - // Auto block resizing algorithm from: - // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c - if (level == 0) { - return block_height; - } - const u32 height{GetMipHeight(level)}; - const u32 default_block_height{GetDefaultBlockHeight(pixel_format)}; - const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height}; - u32 block_height = 16; - while (block_height > 1 && blocks_in_y <= block_height * 4) { - block_height >>= 1; - } - return block_height; -} - -u32 SurfaceParams::GetMipBlockDepth(u32 level) const { - if (level == 0) - return block_depth; - if (target != SurfaceTarget::Texture3D) - return 1; - - const u32 depth{GetMipDepth(level)}; - u32 block_depth = 32; - while (block_depth > 1 && depth * 2 <= block_depth) { - block_depth >>= 1; - } - if (block_depth == 32 && GetMipBlockHeight(level) >= 4) { - return 16; - } - return block_depth; -} - -std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { - std::size_t offset = 0; - for (u32 i = 0; i < level; i++) { - offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false); - } - return offset; -} - -std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { - std::size_t offset = 0; - for (u32 i = 0; i < level; i++) { - offset += GetInnerMipmapMemorySize(i, true, false, false); - } - return offset; -} - -std::size_t SurfaceParams::GetGuestLayerSize() const { - return GetInnerMemorySize(false, true, false); -} - -std::size_t SurfaceParams::GetHostLayerSize(u32 level) const { - return GetInnerMipmapMemorySize(level, true, IsLayered(), false); -} - -bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const { - if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) != - std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format, - view_params.component_type, view_params.type)) { - return false; - } - - const SurfaceTarget view_target{view_params.target}; - if (view_target == target) { - return true; - } - - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture3D: - return false; - case SurfaceTarget::Texture1DArray: - return view_target == SurfaceTarget::Texture1D; - case SurfaceTarget::Texture2DArray: - return view_target == SurfaceTarget::Texture2D; - case SurfaceTarget::TextureCubemap: - return view_target == SurfaceTarget::Texture2D || - view_target == SurfaceTarget::Texture2DArray; - case SurfaceTarget::TextureCubeArray: - return view_target == SurfaceTarget::Texture2D || - view_target == SurfaceTarget::Texture2DArray || - view_target == SurfaceTarget::TextureCubemap; - default: - UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast(target)); - return false; - } -} - -bool SurfaceParams::IsPixelFormatZeta() const { - return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && - pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; -} - -void SurfaceParams::CalculateCachedValues() { - guest_size_in_bytes = GetInnerMemorySize(false, false, false); - - // ASTC is uncompressed in software, in emulated as RGBA8 - if (IsPixelFormatASTC(pixel_format)) { - host_size_in_bytes = width * height * depth * 4; - } else { - host_size_in_bytes = GetInnerMemorySize(true, false, false); - } - - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture3D: - num_layers = 1; - break; - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: - num_layers = depth; - break; - default: - UNREACHABLE(); - } -} - -std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, - bool uncompressed) const { - const bool tiled{as_host_size ? false : is_tiled}; - const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; - const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; - const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)}; - const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)}; - const u32 depth{layer_only ? 1U : GetMipDepth(level)}; - return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height, - depth, GetMipBlockHeight(level), GetMipBlockDepth(level)); -} - -std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only, - bool uncompressed) const { - std::size_t size = 0; - for (u32 level = 0; level < num_levels; ++level) { - size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed); - } - if (!as_host_size && is_tiled) { - size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth); - } - return size; -} - -std::map> SurfaceParams::CreateViewOffsetMap() const { - std::map> view_offset_map; - switch (target) { - case SurfaceTarget::Texture1D: - case SurfaceTarget::Texture2D: - case SurfaceTarget::Texture3D: { - constexpr u32 layer = 0; - for (u32 level = 0; level < num_levels; ++level) { - const std::size_t offset{GetGuestMipmapLevelOffset(level)}; - view_offset_map.insert({offset, {layer, level}}); - } - break; - } - case SurfaceTarget::Texture1DArray: - case SurfaceTarget::Texture2DArray: - case SurfaceTarget::TextureCubemap: - case SurfaceTarget::TextureCubeArray: { - const std::size_t layer_size{GetGuestLayerSize()}; - for (u32 level = 0; level < num_levels; ++level) { - const std::size_t level_offset{GetGuestMipmapLevelOffset(level)}; - for (u32 layer = 0; layer < num_layers; ++layer) { - const auto layer_offset{static_cast(layer_size * layer)}; - const std::size_t offset{level_offset + layer_offset}; - view_offset_map.insert({offset, {layer, level}}); - } - } - break; - } - default: - UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast(target)); - } - return view_offset_map; -} - -bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const { - return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) && - IsInBounds(view_params, layer, level); -} - -bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const { - return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level); -} - -bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const { - if (view_params.target != SurfaceTarget::Texture3D) { - return true; - } - return view_params.depth == GetMipDepth(level); -} - -bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const { - return layer + view_params.num_layers <= num_layers && - level + view_params.num_levels <= num_levels; -} - -std::size_t HasheableSurfaceParams::Hash() const { - return static_cast( - Common::CityHash64(reinterpret_cast(this), sizeof(*this))); -} - -bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const { - return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, - height, depth, pitch, unaligned_height, num_levels, pixel_format, - component_type, type, target) == - std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, - rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, - rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type, - rhs.type, rhs.target); -} - -std::size_t ViewKey::Hash() const { - return static_cast( - Common::CityHash64(reinterpret_cast(this), sizeof(*this))); -} - -bool ViewKey::operator==(const ViewKey& rhs) const { - return std::tie(base_layer, num_layers, base_level, num_levels) == - std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels); -} - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h deleted file mode 100644 index 0415516910..0000000000 --- a/src/video_core/texture_cache.h +++ /dev/null @@ -1,586 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "common/assert.h" -#include "common/common_types.h" -#include "core/memory.h" -#include "video_core/engines/fermi_2d.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/gpu.h" -#include "video_core/rasterizer_interface.h" -#include "video_core/surface.h" - -namespace Core { -class System; -} - -namespace Tegra::Texture { -struct FullTextureInfo; -} - -namespace VideoCore { -class RasterizerInterface; -} - -namespace VideoCommon { - -class HasheableSurfaceParams { -public: - std::size_t Hash() const; - - bool operator==(const HasheableSurfaceParams& rhs) const; - -protected: - // Avoid creation outside of a managed environment. - HasheableSurfaceParams() = default; - - bool is_tiled; - u32 block_width; - u32 block_height; - u32 block_depth; - u32 tile_width_spacing; - u32 width; - u32 height; - u32 depth; - u32 pitch; - u32 unaligned_height; - u32 num_levels; - VideoCore::Surface::PixelFormat pixel_format; - VideoCore::Surface::ComponentType component_type; - VideoCore::Surface::SurfaceType type; - VideoCore::Surface::SurfaceTarget target; -}; - -class SurfaceParams final : public HasheableSurfaceParams { -public: - /// Creates SurfaceCachedParams from a texture configuration. - static SurfaceParams CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config); - - /// Creates SurfaceCachedParams for a depth buffer configuration. - static SurfaceParams CreateForDepthBuffer( - Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); - - /// Creates SurfaceCachedParams from a framebuffer configuration. - static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); - - /// Creates SurfaceCachedParams from a Fermi2D surface configuration. - static SurfaceParams CreateForFermiCopySurface( - const Tegra::Engines::Fermi2D::Regs::Surface& config); - - bool IsTiled() const { - return is_tiled; - } - - u32 GetBlockWidth() const { - return block_width; - } - - u32 GetTileWidthSpacing() const { - return tile_width_spacing; - } - - u32 GetWidth() const { - return width; - } - - u32 GetHeight() const { - return height; - } - - u32 GetDepth() const { - return depth; - } - - u32 GetPitch() const { - return pitch; - } - - u32 GetNumLevels() const { - return num_levels; - } - - VideoCore::Surface::PixelFormat GetPixelFormat() const { - return pixel_format; - } - - VideoCore::Surface::ComponentType GetComponentType() const { - return component_type; - } - - VideoCore::Surface::SurfaceTarget GetTarget() const { - return target; - } - - VideoCore::Surface::SurfaceType GetType() const { - return type; - } - - std::size_t GetGuestSizeInBytes() const { - return guest_size_in_bytes; - } - - std::size_t GetHostSizeInBytes() const { - return host_size_in_bytes; - } - - u32 GetNumLayers() const { - return num_layers; - } - - /// Returns the width of a given mipmap level. - u32 GetMipWidth(u32 level) const; - - /// Returns the height of a given mipmap level. - u32 GetMipHeight(u32 level) const; - - /// Returns the depth of a given mipmap level. - u32 GetMipDepth(u32 level) const; - - /// Returns true if these parameters are from a layered surface. - bool IsLayered() const; - - /// Returns the block height of a given mipmap level. - u32 GetMipBlockHeight(u32 level) const; - - /// Returns the block depth of a given mipmap level. - u32 GetMipBlockDepth(u32 level) const; - - /// Returns the offset in bytes in guest memory of a given mipmap level. - std::size_t GetGuestMipmapLevelOffset(u32 level) const; - - /// Returns the offset in bytes in host memory (linear) of a given mipmap level. - std::size_t GetHostMipmapLevelOffset(u32 level) const; - - /// Returns the size of a layer in bytes in guest memory. - std::size_t GetGuestLayerSize() const; - - /// Returns the size of a layer in bytes in host memory for a given mipmap level. - std::size_t GetHostLayerSize(u32 level) const; - - /// Returns true if another surface can be familiar with this. This is a loosely defined term - /// that reflects the possibility of these two surface parameters potentially being part of a - /// bigger superset. - bool IsFamiliar(const SurfaceParams& view_params) const; - - /// Returns true if the pixel format is a depth and/or stencil format. - bool IsPixelFormatZeta() const; - - /// Creates a map that redirects an address difference to a layer and mipmap level. - std::map> CreateViewOffsetMap() const; - - /// Returns true if the passed surface view parameters is equal or a valid subset of this. - bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const; - -private: - /// Calculates values that can be deduced from HasheableSurfaceParams. - void CalculateCachedValues(); - - /// Returns the size of a given mipmap level. - std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, - bool uncompressed) const; - - /// Returns the size of all mipmap levels and aligns as needed. - std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const; - - /// Returns true if the passed view width and height match the size of this params in a given - /// mipmap level. - bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const; - - /// Returns true if the passed view depth match the size of this params in a given mipmap level. - bool IsDepthValid(const SurfaceParams& view_params, u32 level) const; - - /// Returns true if the passed view layers and mipmap levels are in bounds. - bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const; - - std::size_t guest_size_in_bytes; - std::size_t host_size_in_bytes; - u32 num_layers; -}; - -struct ViewKey { - std::size_t Hash() const; - - bool operator==(const ViewKey& rhs) const; - - u32 base_layer{}; - u32 num_layers{}; - u32 base_level{}; - u32 num_levels{}; -}; - -} // namespace VideoCommon - -namespace std { - -template <> -struct hash { - std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { - return k.Hash(); - } -}; - -template <> -struct hash { - std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept { - return k.Hash(); - } -}; - -} // namespace std - -namespace VideoCommon { - -template -class SurfaceBase { - static_assert(std::is_trivially_copyable_v); - -public: - virtual void LoadBuffer() = 0; - - virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0; - - virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0; - - TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) { - if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) { - // It can't be a view if it's in a prior address. - return {}; - } - - const auto relative_offset{static_cast(view_addr - cpu_addr)}; - const auto it{view_offset_map.find(relative_offset)}; - if (it == view_offset_map.end()) { - // Couldn't find an aligned view. - return {}; - } - const auto [layer, level] = it->second; - - if (!params.IsViewValid(view_params, layer, level)) { - return {}; - } - - return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels()); - } - - VAddr GetCpuAddr() const { - ASSERT(is_registered); - return cpu_addr; - } - - u8* GetHostPtr() const { - ASSERT(is_registered); - return host_ptr; - } - - CacheAddr GetCacheAddr() const { - ASSERT(is_registered); - return cache_addr; - } - - std::size_t GetSizeInBytes() const { - return params.GetGuestSizeInBytes(); - } - - void MarkAsModified(bool is_modified_) { - is_modified = is_modified_; - } - - const SurfaceParams& GetSurfaceParams() const { - return params; - } - - TView* GetView(VAddr view_addr, const SurfaceParams& view_params) { - TView* view{TryGetView(view_addr, view_params)}; - ASSERT(view != nullptr); - return view; - } - - void Register(VAddr cpu_addr_, u8* host_ptr_) { - ASSERT(!is_registered); - is_registered = true; - cpu_addr = cpu_addr_; - host_ptr = host_ptr_; - cache_addr = ToCacheAddr(host_ptr_); - } - - void Register(VAddr cpu_addr_) { - Register(cpu_addr_, Memory::GetPointer(cpu_addr_)); - } - - void Unregister() { - ASSERT(is_registered); - is_registered = false; - } - - bool IsRegistered() const { - return is_registered; - } - -protected: - explicit SurfaceBase(const SurfaceParams& params) - : params{params}, view_offset_map{params.CreateViewOffsetMap()} {} - - ~SurfaceBase() = default; - - virtual std::unique_ptr CreateView(const ViewKey& view_key) = 0; - - bool IsModified() const { - return is_modified; - } - - const SurfaceParams params; - -private: - TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) { - const ViewKey key{base_layer, num_layers, base_level, num_levels}; - const auto [entry, is_cache_miss] = views.try_emplace(key); - auto& view{entry->second}; - if (is_cache_miss) { - view = CreateView(key); - } - return view.get(); - } - - const std::map> view_offset_map; - - VAddr cpu_addr{}; - u8* host_ptr{}; - CacheAddr cache_addr{}; - bool is_modified{}; - bool is_registered{}; - std::unordered_map> views; -}; - -template -class TextureCache { - static_assert(std::is_trivially_copyable_v); - using ResultType = std::tuple; - using IntervalMap = boost::icl::interval_map>; - using IntervalType = typename IntervalMap::interval_type; - -public: - void InvalidateRegion(CacheAddr addr, std::size_t size) { - for (TSurface* surface : GetSurfacesInRegion(addr, size)) { - if (!surface->IsRegistered()) { - // Skip duplicates - continue; - } - Unregister(surface); - } - } - - ResultType GetTextureSurface(TExecutionContext exctx, - const Tegra::Texture::FullTextureInfo& config) { - auto& memory_manager{system.GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())}; - if (!cpu_addr) { - return {{}, exctx}; - } - const auto params{SurfaceParams::CreateForTexture(system, config)}; - return GetSurfaceView(exctx, *cpu_addr, params, true); - } - - ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) { - const auto& regs{system.GPU().Maxwell3D().regs}; - if (!regs.zeta.Address() || !regs.zeta_enable) { - return {{}, exctx}; - } - - auto& memory_manager{system.GPU().MemoryManager()}; - const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())}; - if (!cpu_addr) { - return {{}, exctx}; - } - - const auto depth_params{SurfaceParams::CreateForDepthBuffer( - system, regs.zeta_width, regs.zeta_height, regs.zeta.format, - regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, - regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; - return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents); - } - - ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index, - bool preserve_contents) { - ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); - - const auto& regs{system.GPU().Maxwell3D().regs}; - if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || - regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { - return {{}, exctx}; - } - - auto& memory_manager{system.GPU().MemoryManager()}; - const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; - const auto cpu_addr{memory_manager.GpuToCpuAddress( - config.Address() + config.base_layer * config.layer_stride * sizeof(u32))}; - if (!cpu_addr) { - return {{}, exctx}; - } - - return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), - preserve_contents); - } - - ResultType GetFermiSurface(TExecutionContext exctx, - const Tegra::Engines::Fermi2D::Regs::Surface& config) { - const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())}; - ASSERT(cpu_addr); - return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config), - true); - } - - TSurface* TryFindFramebufferSurface(const u8* host_ptr) const { - const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))}; - return it != registered_surfaces.end() ? *it->second.begin() : nullptr; - } - -protected: - TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) - : system{system}, rasterizer{rasterizer} {} - - ~TextureCache() = default; - - virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, - const SurfaceParams& params, bool preserve_contents, - const std::vector& overlaps) = 0; - - virtual std::unique_ptr CreateSurface(const SurfaceParams& params) = 0; - - void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) { - surface->Register(cpu_addr, host_ptr); - registered_surfaces.add({GetSurfaceInterval(surface), {surface}}); - rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1); - } - - void Unregister(TSurface* surface) { - registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}}); - rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1); - surface->Unregister(); - } - - TSurface* GetUncachedSurface(const SurfaceParams& params) { - if (TSurface* surface = TryGetReservedSurface(params); surface) - return surface; - // No reserved surface available, create a new one and reserve it - auto new_surface{CreateSurface(params)}; - TSurface* surface{new_surface.get()}; - ReserveSurface(params, std::move(new_surface)); - return surface; - } - - Core::System& system; - -private: - ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params, - bool preserve_contents) { - const auto host_ptr{Memory::GetPointer(cpu_addr)}; - const auto cache_addr{ToCacheAddr(host_ptr)}; - const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())}; - if (overlaps.empty()) { - return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); - } - - if (overlaps.size() == 1) { - if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view) - return {view, exctx}; - } - - TView* fast_view; - std::tie(fast_view, exctx) = - TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps); - - for (TSurface* surface : overlaps) { - if (!fast_view) { - // Flush even when we don't care about the contents, to preserve memory not written - // by the new surface. - exctx = surface->FlushBuffer(exctx); - } - Unregister(surface); - } - - if (fast_view) { - return {fast_view, exctx}; - } - - return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); - } - - ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, - const SurfaceParams& params, bool preserve_contents) { - TSurface* new_surface{GetUncachedSurface(params)}; - Register(new_surface, cpu_addr, host_ptr); - if (preserve_contents) { - exctx = LoadSurface(exctx, new_surface); - } - return {new_surface->GetView(cpu_addr, params), exctx}; - } - - TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) { - surface->LoadBuffer(); - exctx = surface->UploadTexture(exctx); - surface->MarkAsModified(false); - return exctx; - } - - std::vector GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const { - if (size == 0) { - return {}; - } - const IntervalType interval{cache_addr, cache_addr + size}; - - std::vector surfaces; - for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) { - surfaces.push_back(*pair.second.begin()); - } - return surfaces; - } - - void ReserveSurface(const SurfaceParams& params, std::unique_ptr surface) { - surface_reserve[params].push_back(std::move(surface)); - } - - TSurface* TryGetReservedSurface(const SurfaceParams& params) { - auto search{surface_reserve.find(params)}; - if (search == surface_reserve.end()) { - return {}; - } - for (auto& surface : search->second) { - if (!surface->IsRegistered()) { - return surface.get(); - } - } - return {}; - } - - IntervalType GetSurfaceInterval(TSurface* surface) const { - return IntervalType::right_open(surface->GetCacheAddr(), - surface->GetCacheAddr() + surface->GetSizeInBytes()); - } - - VideoCore::RasterizerInterface& rasterizer; - - IntervalMap registered_surfaces; - - /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have - /// previously been used. This is to prevent surfaces from being constantly created and - /// destroyed when used with different surface parameters. - std::unordered_map>> surface_reserve; -}; - -} // namespace VideoCommon diff --git a/src/video_core/texture_cache/copy_params.h b/src/video_core/texture_cache/copy_params.h new file mode 100644 index 0000000000..9c21a06499 --- /dev/null +++ b/src/video_core/texture_cache/copy_params.h @@ -0,0 +1,36 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +namespace VideoCommon { + +struct CopyParams { + constexpr CopyParams(u32 source_x, u32 source_y, u32 source_z, u32 dest_x, u32 dest_y, + u32 dest_z, u32 source_level, u32 dest_level, u32 width, u32 height, + u32 depth) + : source_x{source_x}, source_y{source_y}, source_z{source_z}, dest_x{dest_x}, + dest_y{dest_y}, dest_z{dest_z}, source_level{source_level}, + dest_level{dest_level}, width{width}, height{height}, depth{depth} {} + + constexpr CopyParams(u32 width, u32 height, u32 depth, u32 level) + : source_x{}, source_y{}, source_z{}, dest_x{}, dest_y{}, dest_z{}, source_level{level}, + dest_level{level}, width{width}, height{height}, depth{depth} {} + + u32 source_x; + u32 source_y; + u32 source_z; + u32 dest_x; + u32 dest_y; + u32 dest_z; + u32 source_level; + u32 dest_level; + u32 width; + u32 height; + u32 depth; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp new file mode 100644 index 0000000000..7a0fdb19bc --- /dev/null +++ b/src/video_core/texture_cache/surface_base.cpp @@ -0,0 +1,300 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/microprofile.h" +#include "video_core/memory_manager.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/surface_params.h" +#include "video_core/textures/convert.h" + +namespace VideoCommon { + +MICROPROFILE_DEFINE(GPU_Load_Texture, "GPU", "Texture Load", MP_RGB(128, 192, 128)); +MICROPROFILE_DEFINE(GPU_Flush_Texture, "GPU", "Texture Flush", MP_RGB(128, 192, 128)); + +using Tegra::Texture::ConvertFromGuestToHost; +using VideoCore::MortonSwizzleMode; +using VideoCore::Surface::SurfaceCompression; + +StagingCache::StagingCache() = default; + +StagingCache::~StagingCache() = default; + +SurfaceBaseImpl::SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params) + : params{params}, mipmap_sizes(params.num_levels), + mipmap_offsets(params.num_levels), gpu_addr{gpu_addr}, host_memory_size{ + params.GetHostSizeInBytes()} { + std::size_t offset = 0; + for (u32 level = 0; level < params.num_levels; ++level) { + const std::size_t mipmap_size{params.GetGuestMipmapSize(level)}; + mipmap_sizes[level] = mipmap_size; + mipmap_offsets[level] = offset; + offset += mipmap_size; + } + layer_size = offset; + if (params.is_layered) { + if (params.is_tiled) { + layer_size = + SurfaceParams::AlignLayered(layer_size, params.block_height, params.block_depth); + } + guest_memory_size = layer_size * params.depth; + } else { + guest_memory_size = layer_size; + } +} + +MatchTopologyResult SurfaceBaseImpl::MatchesTopology(const SurfaceParams& rhs) const { + const u32 src_bpp{params.GetBytesPerPixel()}; + const u32 dst_bpp{rhs.GetBytesPerPixel()}; + const bool ib1 = params.IsBuffer(); + const bool ib2 = rhs.IsBuffer(); + if (std::tie(src_bpp, params.is_tiled, ib1) == std::tie(dst_bpp, rhs.is_tiled, ib2)) { + const bool cb1 = params.IsCompressed(); + const bool cb2 = rhs.IsCompressed(); + if (cb1 == cb2) { + return MatchTopologyResult::FullMatch; + } + return MatchTopologyResult::CompressUnmatch; + } + return MatchTopologyResult::None; +} + +MatchStructureResult SurfaceBaseImpl::MatchesStructure(const SurfaceParams& rhs) const { + // Buffer surface Check + if (params.IsBuffer()) { + const std::size_t wd1 = params.width * params.GetBytesPerPixel(); + const std::size_t wd2 = rhs.width * rhs.GetBytesPerPixel(); + if (wd1 == wd2) { + return MatchStructureResult::FullMatch; + } + return MatchStructureResult::None; + } + + // Linear Surface check + if (!params.is_tiled) { + if (std::tie(params.width, params.height, params.pitch) == + std::tie(rhs.width, rhs.height, rhs.pitch)) { + return MatchStructureResult::FullMatch; + } + return MatchStructureResult::None; + } + + // Tiled Surface check + if (std::tie(params.depth, params.block_width, params.block_height, params.block_depth, + params.tile_width_spacing, params.num_levels) == + std::tie(rhs.depth, rhs.block_width, rhs.block_height, rhs.block_depth, + rhs.tile_width_spacing, rhs.num_levels)) { + if (std::tie(params.width, params.height) == std::tie(rhs.width, rhs.height)) { + return MatchStructureResult::FullMatch; + } + const u32 ws = SurfaceParams::ConvertWidth(rhs.GetBlockAlignedWidth(), params.pixel_format, + rhs.pixel_format); + const u32 hs = + SurfaceParams::ConvertHeight(rhs.height, params.pixel_format, rhs.pixel_format); + const u32 w1 = params.GetBlockAlignedWidth(); + if (std::tie(w1, params.height) == std::tie(ws, hs)) { + return MatchStructureResult::SemiMatch; + } + } + return MatchStructureResult::None; +} + +std::optional> SurfaceBaseImpl::GetLayerMipmap( + const GPUVAddr candidate_gpu_addr) const { + if (gpu_addr == candidate_gpu_addr) { + return {{0, 0}}; + } + if (candidate_gpu_addr < gpu_addr) { + return {}; + } + const auto relative_address{static_cast(candidate_gpu_addr - gpu_addr)}; + const auto layer{static_cast(relative_address / layer_size)}; + const GPUVAddr mipmap_address = relative_address - layer_size * layer; + const auto mipmap_it = + Common::BinaryFind(mipmap_offsets.begin(), mipmap_offsets.end(), mipmap_address); + if (mipmap_it == mipmap_offsets.end()) { + return {}; + } + const auto level{static_cast(std::distance(mipmap_offsets.begin(), mipmap_it))}; + return std::make_pair(layer, level); +} + +std::vector SurfaceBaseImpl::BreakDownLayered(const SurfaceParams& in_params) const { + const u32 layers{params.depth}; + const u32 mipmaps{params.num_levels}; + std::vector result; + result.reserve(static_cast(layers) * static_cast(mipmaps)); + + for (u32 layer = 0; layer < layers; layer++) { + for (u32 level = 0; level < mipmaps; level++) { + const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); + const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); + result.emplace_back(width, height, layer, level); + } + } + return result; +} + +std::vector SurfaceBaseImpl::BreakDownNonLayered(const SurfaceParams& in_params) const { + const u32 mipmaps{params.num_levels}; + std::vector result; + result.reserve(mipmaps); + + for (u32 level = 0; level < mipmaps; level++) { + const u32 width = SurfaceParams::IntersectWidth(params, in_params, level, level); + const u32 height = SurfaceParams::IntersectHeight(params, in_params, level, level); + const u32 depth{std::min(params.GetMipDepth(level), in_params.GetMipDepth(level))}; + result.emplace_back(width, height, depth, level); + } + return result; +} + +void SurfaceBaseImpl::SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, + u8* buffer, u32 level) { + const u32 width{params.GetMipWidth(level)}; + const u32 height{params.GetMipHeight(level)}; + const u32 block_height{params.GetMipBlockHeight(level)}; + const u32 block_depth{params.GetMipBlockDepth(level)}; + + std::size_t guest_offset{mipmap_offsets[level]}; + if (params.is_layered) { + std::size_t host_offset{0}; + const std::size_t guest_stride = layer_size; + const std::size_t host_stride = params.GetHostLayerSize(level); + for (u32 layer = 0; layer < params.depth; ++layer) { + MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, 1, + params.tile_width_spacing, buffer + host_offset, memory + guest_offset); + guest_offset += guest_stride; + host_offset += host_stride; + } + } else { + MortonSwizzle(mode, params.pixel_format, width, block_height, height, block_depth, + params.GetMipDepth(level), params.tile_width_spacing, buffer, + memory + guest_offset); + } +} + +void SurfaceBaseImpl::LoadBuffer(Tegra::MemoryManager& memory_manager, + StagingCache& staging_cache) { + MICROPROFILE_SCOPE(GPU_Load_Texture); + auto& staging_buffer = staging_cache.GetBuffer(0); + u8* host_ptr; + is_continuous = memory_manager.IsBlockContinuous(gpu_addr, guest_memory_size); + + // Handle continuouty + if (is_continuous) { + // Use physical memory directly + host_ptr = memory_manager.GetPointer(gpu_addr); + if (!host_ptr) { + return; + } + } else { + // Use an extra temporal buffer + auto& tmp_buffer = staging_cache.GetBuffer(1); + tmp_buffer.resize(guest_memory_size); + host_ptr = tmp_buffer.data(); + memory_manager.ReadBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); + } + + if (params.is_tiled) { + ASSERT_MSG(params.block_width == 0, "Block width is defined as {} on texture target {}", + params.block_width, static_cast(params.target)); + for (u32 level = 0; level < params.num_levels; ++level) { + const std::size_t host_offset{params.GetHostMipmapLevelOffset(level)}; + SwizzleFunc(MortonSwizzleMode::MortonToLinear, host_ptr, params, + staging_buffer.data() + host_offset, level); + } + } else { + ASSERT_MSG(params.num_levels == 1, "Linear mipmap loading is not implemented"); + const u32 bpp{params.GetBytesPerPixel()}; + const u32 block_width{params.GetDefaultBlockWidth()}; + const u32 block_height{params.GetDefaultBlockHeight()}; + const u32 width{(params.width + block_width - 1) / block_width}; + const u32 height{(params.height + block_height - 1) / block_height}; + const u32 copy_size{width * bpp}; + if (params.pitch == copy_size) { + std::memcpy(staging_buffer.data(), host_ptr, params.GetHostSizeInBytes()); + } else { + const u8* start{host_ptr}; + u8* write_to{staging_buffer.data()}; + for (u32 h = height; h > 0; --h) { + std::memcpy(write_to, start, copy_size); + start += params.pitch; + write_to += copy_size; + } + } + } + + auto compression_type = params.GetCompressionType(); + if (compression_type == SurfaceCompression::None || + compression_type == SurfaceCompression::Compressed) + return; + + for (u32 level_up = params.num_levels; level_up > 0; --level_up) { + const u32 level = level_up - 1; + const std::size_t in_host_offset{params.GetHostMipmapLevelOffset(level)}; + const std::size_t out_host_offset = compression_type == SurfaceCompression::Rearranged + ? in_host_offset + : params.GetConvertedMipmapOffset(level); + u8* in_buffer = staging_buffer.data() + in_host_offset; + u8* out_buffer = staging_buffer.data() + out_host_offset; + ConvertFromGuestToHost(in_buffer, out_buffer, params.pixel_format, + params.GetMipWidth(level), params.GetMipHeight(level), + params.GetMipDepth(level), true, true); + } +} + +void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager, + StagingCache& staging_cache) { + MICROPROFILE_SCOPE(GPU_Flush_Texture); + auto& staging_buffer = staging_cache.GetBuffer(0); + u8* host_ptr; + + // Handle continuouty + if (is_continuous) { + // Use physical memory directly + host_ptr = memory_manager.GetPointer(gpu_addr); + if (!host_ptr) { + return; + } + } else { + // Use an extra temporal buffer + auto& tmp_buffer = staging_cache.GetBuffer(1); + tmp_buffer.resize(guest_memory_size); + host_ptr = tmp_buffer.data(); + } + + if (params.is_tiled) { + ASSERT_MSG(params.block_width == 0, "Block width is defined as {}", params.block_width); + for (u32 level = 0; level < params.num_levels; ++level) { + const std::size_t host_offset{params.GetHostMipmapLevelOffset(level)}; + SwizzleFunc(MortonSwizzleMode::LinearToMorton, host_ptr, params, + staging_buffer.data() + host_offset, level); + } + } else { + ASSERT(params.target == SurfaceTarget::Texture2D); + ASSERT(params.num_levels == 1); + + const u32 bpp{params.GetBytesPerPixel()}; + const u32 copy_size{params.width * bpp}; + if (params.pitch == copy_size) { + std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size); + } else { + u8* start{host_ptr}; + const u8* read_to{staging_buffer.data()}; + for (u32 h = params.height; h > 0; --h) { + std::memcpy(start, read_to, copy_size); + start += params.pitch; + read_to += copy_size; + } + } + } + if (!is_continuous) { + memory_manager.WriteBlockUnsafe(gpu_addr, host_ptr, guest_memory_size); + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h new file mode 100644 index 0000000000..8ba386a8ac --- /dev/null +++ b/src/video_core/texture_cache/surface_base.h @@ -0,0 +1,317 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#include "common/assert.h" +#include "common/binary_find.h" +#include "common/common_types.h" +#include "video_core/gpu.h" +#include "video_core/morton.h" +#include "video_core/texture_cache/copy_params.h" +#include "video_core/texture_cache/surface_params.h" +#include "video_core/texture_cache/surface_view.h" + +namespace Tegra { +class MemoryManager; +} + +namespace VideoCommon { + +using VideoCore::MortonSwizzleMode; +using VideoCore::Surface::SurfaceTarget; + +enum class MatchStructureResult : u32 { + FullMatch = 0, + SemiMatch = 1, + None = 2, +}; + +enum class MatchTopologyResult : u32 { + FullMatch = 0, + CompressUnmatch = 1, + None = 2, +}; + +class StagingCache { +public: + explicit StagingCache(); + ~StagingCache(); + + std::vector& GetBuffer(std::size_t index) { + return staging_buffer[index]; + } + + const std::vector& GetBuffer(std::size_t index) const { + return staging_buffer[index]; + } + + void SetSize(std::size_t size) { + staging_buffer.resize(size); + } + +private: + std::vector> staging_buffer; +}; + +class SurfaceBaseImpl { +public: + void LoadBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache); + + void FlushBuffer(Tegra::MemoryManager& memory_manager, StagingCache& staging_cache); + + GPUVAddr GetGpuAddr() const { + return gpu_addr; + } + + bool Overlaps(const CacheAddr start, const CacheAddr end) const { + return (cache_addr < end) && (cache_addr_end > start); + } + + bool IsInside(const GPUVAddr other_start, const GPUVAddr other_end) { + const GPUVAddr gpu_addr_end = gpu_addr + guest_memory_size; + return (gpu_addr <= other_start && other_end <= gpu_addr_end); + } + + // Use only when recycling a surface + void SetGpuAddr(const GPUVAddr new_addr) { + gpu_addr = new_addr; + } + + VAddr GetCpuAddr() const { + return cpu_addr; + } + + void SetCpuAddr(const VAddr new_addr) { + cpu_addr = new_addr; + } + + CacheAddr GetCacheAddr() const { + return cache_addr; + } + + CacheAddr GetCacheAddrEnd() const { + return cache_addr_end; + } + + void SetCacheAddr(const CacheAddr new_addr) { + cache_addr = new_addr; + cache_addr_end = new_addr + guest_memory_size; + } + + const SurfaceParams& GetSurfaceParams() const { + return params; + } + + std::size_t GetSizeInBytes() const { + return guest_memory_size; + } + + std::size_t GetHostSizeInBytes() const { + return host_memory_size; + } + + std::size_t GetMipmapSize(const u32 level) const { + return mipmap_sizes[level]; + } + + void MarkAsContinuous(const bool is_continuous) { + this->is_continuous = is_continuous; + } + + bool IsContinuous() const { + return is_continuous; + } + + bool IsLinear() const { + return !params.is_tiled; + } + + bool MatchFormat(VideoCore::Surface::PixelFormat pixel_format) const { + return params.pixel_format == pixel_format; + } + + VideoCore::Surface::PixelFormat GetFormat() const { + return params.pixel_format; + } + + bool MatchTarget(VideoCore::Surface::SurfaceTarget target) const { + return params.target == target; + } + + MatchTopologyResult MatchesTopology(const SurfaceParams& rhs) const; + + MatchStructureResult MatchesStructure(const SurfaceParams& rhs) const; + + bool MatchesSubTexture(const SurfaceParams& rhs, const GPUVAddr other_gpu_addr) const { + return std::tie(gpu_addr, params.target, params.num_levels) == + std::tie(other_gpu_addr, rhs.target, rhs.num_levels) && + params.target == SurfaceTarget::Texture2D && params.num_levels == 1; + } + + std::optional> GetLayerMipmap(const GPUVAddr candidate_gpu_addr) const; + + std::vector BreakDown(const SurfaceParams& in_params) const { + return params.is_layered ? BreakDownLayered(in_params) : BreakDownNonLayered(in_params); + } + +protected: + explicit SurfaceBaseImpl(GPUVAddr gpu_addr, const SurfaceParams& params); + ~SurfaceBaseImpl() = default; + + virtual void DecorateSurfaceName() = 0; + + const SurfaceParams params; + std::size_t layer_size; + std::size_t guest_memory_size; + const std::size_t host_memory_size; + GPUVAddr gpu_addr{}; + CacheAddr cache_addr{}; + CacheAddr cache_addr_end{}; + VAddr cpu_addr{}; + bool is_continuous{}; + + std::vector mipmap_sizes; + std::vector mipmap_offsets; + +private: + void SwizzleFunc(MortonSwizzleMode mode, u8* memory, const SurfaceParams& params, u8* buffer, + u32 level); + + std::vector BreakDownLayered(const SurfaceParams& in_params) const; + + std::vector BreakDownNonLayered(const SurfaceParams& in_params) const; +}; + +template +class SurfaceBase : public SurfaceBaseImpl { +public: + virtual void UploadTexture(const std::vector& staging_buffer) = 0; + + virtual void DownloadTexture(std::vector& staging_buffer) = 0; + + void MarkAsModified(const bool is_modified_, const u64 tick) { + is_modified = is_modified_ || is_target; + modification_tick = tick; + } + + void MarkAsRenderTarget(const bool is_target) { + this->is_target = is_target; + } + + void MarkAsPicked(const bool is_picked) { + this->is_picked = is_picked; + } + + bool IsModified() const { + return is_modified; + } + + bool IsProtected() const { + // Only 3D Slices are to be protected + return is_target && params.block_depth > 0; + } + + bool IsRenderTarget() const { + return is_target; + } + + bool IsRegistered() const { + return is_registered; + } + + bool IsPicked() const { + return is_picked; + } + + void MarkAsRegistered(bool is_reg) { + is_registered = is_reg; + } + + u64 GetModificationTick() const { + return modification_tick; + } + + TView EmplaceOverview(const SurfaceParams& overview_params) { + const u32 num_layers{(params.is_layered && !overview_params.is_layered) ? 1 : params.depth}; + return GetView(ViewParams(overview_params.target, 0, num_layers, 0, params.num_levels)); + } + + std::optional EmplaceIrregularView(const SurfaceParams& view_params, + const GPUVAddr view_addr, + const std::size_t candidate_size, const u32 mipmap, + const u32 layer) { + const auto layer_mipmap{GetLayerMipmap(view_addr + candidate_size)}; + if (!layer_mipmap) { + return {}; + } + const u32 end_layer{layer_mipmap->first}; + const u32 end_mipmap{layer_mipmap->second}; + if (layer != end_layer) { + if (mipmap == 0 && end_mipmap == 0) { + return GetView(ViewParams(view_params.target, layer, end_layer - layer + 1, 0, 1)); + } + return {}; + } else { + return GetView( + ViewParams(view_params.target, layer, 1, mipmap, end_mipmap - mipmap + 1)); + } + } + + std::optional EmplaceView(const SurfaceParams& view_params, const GPUVAddr view_addr, + const std::size_t candidate_size) { + if (params.target == SurfaceTarget::Texture3D || + (params.num_levels == 1 && !params.is_layered) || + view_params.target == SurfaceTarget::Texture3D) { + return {}; + } + const auto layer_mipmap{GetLayerMipmap(view_addr)}; + if (!layer_mipmap) { + return {}; + } + const u32 layer{layer_mipmap->first}; + const u32 mipmap{layer_mipmap->second}; + if (GetMipmapSize(mipmap) != candidate_size) { + return EmplaceIrregularView(view_params, view_addr, candidate_size, mipmap, layer); + } + return GetView(ViewParams(view_params.target, layer, 1, mipmap, 1)); + } + + TView GetMainView() const { + return main_view; + } + +protected: + explicit SurfaceBase(const GPUVAddr gpu_addr, const SurfaceParams& params) + : SurfaceBaseImpl(gpu_addr, params) {} + + ~SurfaceBase() = default; + + virtual TView CreateView(const ViewParams& view_key) = 0; + + TView main_view; + std::unordered_map views; + +private: + TView GetView(const ViewParams& key) { + const auto [entry, is_cache_miss] = views.try_emplace(key); + auto& view{entry->second}; + if (is_cache_miss) { + view = CreateView(key); + } + return view; + } + + bool is_modified{}; + bool is_target{}; + bool is_registered{}; + bool is_picked{}; + u64 modification_tick{}; +}; + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp new file mode 100644 index 0000000000..9c56e2b4f1 --- /dev/null +++ b/src/video_core/texture_cache/surface_params.cpp @@ -0,0 +1,334 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/alignment.h" +#include "common/bit_util.h" +#include "core/core.h" +#include "video_core/engines/shader_bytecode.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/surface_params.h" + +namespace VideoCommon { + +using VideoCore::Surface::ComponentTypeFromDepthFormat; +using VideoCore::Surface::ComponentTypeFromRenderTarget; +using VideoCore::Surface::ComponentTypeFromTexture; +using VideoCore::Surface::PixelFormat; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::PixelFormatFromTextureFormat; +using VideoCore::Surface::SurfaceTarget; +using VideoCore::Surface::SurfaceTargetFromTextureType; +using VideoCore::Surface::SurfaceType; + +SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { + switch (type) { + case Tegra::Shader::TextureType::Texture1D: { + if (is_array) + return SurfaceTarget::Texture1DArray; + else + return SurfaceTarget::Texture1D; + } + case Tegra::Shader::TextureType::Texture2D: { + if (is_array) + return SurfaceTarget::Texture2DArray; + else + return SurfaceTarget::Texture2D; + } + case Tegra::Shader::TextureType::Texture3D: { + ASSERT(!is_array); + return SurfaceTarget::Texture3D; + } + case Tegra::Shader::TextureType::TextureCube: { + if (is_array) + return SurfaceTarget::TextureCubeArray; + else + return SurfaceTarget::TextureCubemap; + } + default: { + UNREACHABLE(); + return SurfaceTarget::Texture2D; + } + } +} + +namespace { +constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { + return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); +} +} // Anonymous namespace + +SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, + const Tegra::Texture::FullTextureInfo& config, + const VideoCommon::Shader::Sampler& entry) { + SurfaceParams params; + params.is_tiled = config.tic.IsTiled(); + params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; + params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), + params.srgb_conversion); + params.type = GetFormatType(params.pixel_format); + if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { + switch (params.pixel_format) { + case PixelFormat::R16U: + case PixelFormat::R16F: { + params.pixel_format = PixelFormat::Z16; + break; + } + case PixelFormat::R32F: { + params.pixel_format = PixelFormat::Z32F; + break; + } + default: { + UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}", + static_cast(params.pixel_format)); + } + } + params.type = GetFormatType(params.pixel_format); + } + params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + // TODO: on 1DBuffer we should use the tic info. + if (!config.tic.IsBuffer()) { + params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray()); + params.width = config.tic.Width(); + params.height = config.tic.Height(); + params.depth = config.tic.Depth(); + params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.num_levels = config.tic.max_mip_level + 1; + params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); + params.is_layered = params.IsLayered(); + } else { + params.target = SurfaceTarget::TextureBuffer; + params.width = config.tic.Width(); + params.pitch = params.width * params.GetBytesPerPixel(); + params.height = 1; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + } + return params; +} + +SurfaceParams SurfaceParams::CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { + SurfaceParams params; + params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.srgb_conversion = false; + params.block_width = std::min(block_width, 5U); + params.block_height = std::min(block_height, 5U); + params.block_depth = std::min(block_depth, 5U); + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromDepthFormat(format); + params.component_type = ComponentTypeFromDepthFormat(format); + params.type = GetFormatType(params.pixel_format); + params.width = zeta_width; + params.height = zeta_height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.pitch = 0; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + return params; +} + +SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { + const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; + SurfaceParams params; + params.is_tiled = + config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || + config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; + params.block_width = config.memory_layout.block_width; + params.block_height = config.memory_layout.block_height; + params.block_depth = config.memory_layout.block_depth; + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + if (params.is_tiled) { + params.pitch = 0; + params.width = config.width; + } else { + const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; + params.pitch = config.width; + params.width = params.pitch / bpp; + } + params.height = config.height; + params.depth = 1; + params.target = SurfaceTarget::Texture2D; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + return params; +} + +SurfaceParams SurfaceParams::CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params{}; + params.is_tiled = !config.linear; + params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB || + config.format == Tegra::RenderTargetFormat::RGBA8_SRGB; + params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 5U) : 0, + params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 5U) : 0, + params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 5U) : 0, + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + params.width = config.width; + params.height = config.height; + params.pitch = config.pitch; + // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = params.IsLayered(); + return params; +} + +bool SurfaceParams::IsLayered() const { + switch (target) { + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + return true; + default: + return false; + } +} + +// Auto block resizing algorithm from: +// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c +u32 SurfaceParams::GetMipBlockHeight(u32 level) const { + if (level == 0) { + return this->block_height; + } + + const u32 height_new{GetMipHeight(level)}; + const u32 default_block_height{GetDefaultBlockHeight()}; + const u32 blocks_in_y{(height_new + default_block_height - 1) / default_block_height}; + const u32 block_height_new = Common::Log2Ceil32(blocks_in_y); + return std::clamp(block_height_new, 3U, 7U) - 3U; +} + +u32 SurfaceParams::GetMipBlockDepth(u32 level) const { + if (level == 0) { + return this->block_depth; + } + if (is_layered) { + return 0; + } + + const u32 depth_new{GetMipDepth(level)}; + const u32 block_depth_new = Common::Log2Ceil32(depth_new); + if (block_depth_new > 4) { + return 5 - (GetMipBlockHeight(level) >= 2); + } + return block_depth_new; +} + +std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, false, false); + } + return offset; +} + +std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, true, false) * GetNumLayers(); + } + return offset; +} + +std::size_t SurfaceParams::GetConvertedMipmapOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetConvertedMipmapSize(i); + } + return offset; +} + +std::size_t SurfaceParams::GetConvertedMipmapSize(u32 level) const { + constexpr std::size_t rgba8_bpp = 4ULL; + const std::size_t width_t = GetMipWidth(level); + const std::size_t height_t = GetMipHeight(level); + const std::size_t depth_t = is_layered ? depth : GetMipDepth(level); + return width_t * height_t * depth_t * rgba8_bpp; +} + +std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) const { + std::size_t size = 0; + for (u32 level = 0; level < num_levels; ++level) { + size += GetInnerMipmapMemorySize(level, as_host_size, uncompressed); + } + if (is_tiled && is_layered) { + return Common::AlignBits(size, + Tegra::Texture::GetGOBSizeShift() + block_height + block_depth); + } + return size; +} + +std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, + bool uncompressed) const { + const bool tiled{as_host_size ? false : is_tiled}; + const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), GetDefaultBlockWidth())}; + const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), GetDefaultBlockHeight())}; + const u32 depth{is_layered ? 1U : GetMipDepth(level)}; + return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(), width, height, depth, + GetMipBlockHeight(level), GetMipBlockDepth(level)); +} + +bool SurfaceParams::operator==(const SurfaceParams& rhs) const { + return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, + height, depth, pitch, num_levels, pixel_format, component_type, type, target) == + std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, + rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, + rhs.num_levels, rhs.pixel_format, rhs.component_type, rhs.type, rhs.target); +} + +std::string SurfaceParams::TargetName() const { + switch (target) { + case SurfaceTarget::Texture1D: + return "1D"; + case SurfaceTarget::TextureBuffer: + return "TexBuffer"; + case SurfaceTarget::Texture2D: + return "2D"; + case SurfaceTarget::Texture3D: + return "3D"; + case SurfaceTarget::Texture1DArray: + return "1DArray"; + case SurfaceTarget::Texture2DArray: + return "2DArray"; + case SurfaceTarget::TextureCubemap: + return "Cube"; + case SurfaceTarget::TextureCubeArray: + return "CubeArray"; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast(target)); + UNREACHABLE(); + return fmt::format("TUK({})", static_cast(target)); + } +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h new file mode 100644 index 0000000000..358d6757c4 --- /dev/null +++ b/src/video_core/texture_cache/surface_params.h @@ -0,0 +1,286 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/alignment.h" +#include "common/bit_util.h" +#include "common/cityhash.h" +#include "common/common_types.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/shader/shader_ir.h" +#include "video_core/surface.h" +#include "video_core/textures/decoders.h" + +namespace VideoCommon { + +using VideoCore::Surface::SurfaceCompression; + +class SurfaceParams { +public: + /// Creates SurfaceCachedParams from a texture configuration. + static SurfaceParams CreateForTexture(Core::System& system, + const Tegra::Texture::FullTextureInfo& config, + const VideoCommon::Shader::Sampler& entry); + + /// Creates SurfaceCachedParams for a depth buffer configuration. + static SurfaceParams CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); + + /// Creates SurfaceCachedParams from a framebuffer configuration. + static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); + + /// Creates SurfaceCachedParams from a Fermi2D surface configuration. + static SurfaceParams CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config); + + std::size_t Hash() const { + return static_cast( + Common::CityHash64(reinterpret_cast(this), sizeof(*this))); + } + + bool operator==(const SurfaceParams& rhs) const; + + bool operator!=(const SurfaceParams& rhs) const { + return !operator==(rhs); + } + + std::size_t GetGuestSizeInBytes() const { + return GetInnerMemorySize(false, false, false); + } + + std::size_t GetHostSizeInBytes() const { + std::size_t host_size_in_bytes; + if (GetCompressionType() == SurfaceCompression::Converted) { + constexpr std::size_t rgb8_bpp = 4ULL; + // ASTC is uncompressed in software, in emulated as RGBA8 + host_size_in_bytes = 0; + for (u32 level = 0; level < num_levels; ++level) { + host_size_in_bytes += GetConvertedMipmapSize(level); + } + } else { + host_size_in_bytes = GetInnerMemorySize(true, false, false); + } + return host_size_in_bytes; + } + + u32 GetBlockAlignedWidth() const { + return Common::AlignUp(width, 64 / GetBytesPerPixel()); + } + + /// Returns the width of a given mipmap level. + u32 GetMipWidth(u32 level) const { + return std::max(1U, width >> level); + } + + /// Returns the height of a given mipmap level. + u32 GetMipHeight(u32 level) const { + return std::max(1U, height >> level); + } + + /// Returns the depth of a given mipmap level. + u32 GetMipDepth(u32 level) const { + return is_layered ? depth : std::max(1U, depth >> level); + } + + /// Returns the block height of a given mipmap level. + u32 GetMipBlockHeight(u32 level) const; + + /// Returns the block depth of a given mipmap level. + u32 GetMipBlockDepth(u32 level) const; + + /// Returns the best possible row/pitch alignment for the surface. + u32 GetRowAlignment(u32 level) const { + const u32 bpp = + GetCompressionType() == SurfaceCompression::Converted ? 4 : GetBytesPerPixel(); + return 1U << Common::CountTrailingZeroes32(GetMipWidth(level) * bpp); + } + + /// Returns the offset in bytes in guest memory of a given mipmap level. + std::size_t GetGuestMipmapLevelOffset(u32 level) const; + + /// Returns the offset in bytes in host memory (linear) of a given mipmap level. + std::size_t GetHostMipmapLevelOffset(u32 level) const; + + /// Returns the offset in bytes in host memory (linear) of a given mipmap level + /// for a texture that is converted in host gpu. + std::size_t GetConvertedMipmapOffset(u32 level) const; + + /// Returns the size in bytes in guest memory of a given mipmap level. + std::size_t GetGuestMipmapSize(u32 level) const { + return GetInnerMipmapMemorySize(level, false, false); + } + + /// Returns the size in bytes in host memory (linear) of a given mipmap level. + std::size_t GetHostMipmapSize(u32 level) const { + return GetInnerMipmapMemorySize(level, true, false) * GetNumLayers(); + } + + std::size_t GetConvertedMipmapSize(u32 level) const; + + /// Returns the size of a layer in bytes in guest memory. + std::size_t GetGuestLayerSize() const { + return GetLayerSize(false, false); + } + + /// Returns the size of a layer in bytes in host memory for a given mipmap level. + std::size_t GetHostLayerSize(u32 level) const { + ASSERT(target != VideoCore::Surface::SurfaceTarget::Texture3D); + return GetInnerMipmapMemorySize(level, true, false); + } + + /// Returns the max possible mipmap that the texture can have in host gpu + u32 MaxPossibleMipmap() const { + const u32 max_mipmap_w = Common::Log2Ceil32(width) + 1U; + const u32 max_mipmap_h = Common::Log2Ceil32(height) + 1U; + const u32 max_mipmap = std::max(max_mipmap_w, max_mipmap_h); + if (target != VideoCore::Surface::SurfaceTarget::Texture3D) + return max_mipmap; + return std::max(max_mipmap, Common::Log2Ceil32(depth) + 1U); + } + + /// Returns if the guest surface is a compressed surface. + bool IsCompressed() const { + return GetDefaultBlockHeight() > 1 || GetDefaultBlockWidth() > 1; + } + + /// Returns the default block width. + u32 GetDefaultBlockWidth() const { + return VideoCore::Surface::GetDefaultBlockWidth(pixel_format); + } + + /// Returns the default block height. + u32 GetDefaultBlockHeight() const { + return VideoCore::Surface::GetDefaultBlockHeight(pixel_format); + } + + /// Returns the bits per pixel. + u32 GetBitsPerPixel() const { + return VideoCore::Surface::GetFormatBpp(pixel_format); + } + + /// Returns the bytes per pixel. + u32 GetBytesPerPixel() const { + return VideoCore::Surface::GetBytesPerPixel(pixel_format); + } + + /// Returns true if the pixel format is a depth and/or stencil format. + bool IsPixelFormatZeta() const { + return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && + pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; + } + + /// Returns how the compression should be handled for this texture. + SurfaceCompression GetCompressionType() const { + return VideoCore::Surface::GetFormatCompressionType(pixel_format); + } + + /// Returns is the surface is a TextureBuffer type of surface. + bool IsBuffer() const { + return target == VideoCore::Surface::SurfaceTarget::TextureBuffer; + } + + /// Returns the debug name of the texture for use in graphic debuggers. + std::string TargetName() const; + + // Helper used for out of class size calculations + static std::size_t AlignLayered(const std::size_t out_size, const u32 block_height, + const u32 block_depth) { + return Common::AlignBits(out_size, + Tegra::Texture::GetGOBSizeShift() + block_height + block_depth); + } + + /// Converts a width from a type of surface into another. This helps represent the + /// equivalent value between compressed/non-compressed textures. + static u32 ConvertWidth(u32 width, VideoCore::Surface::PixelFormat pixel_format_from, + VideoCore::Surface::PixelFormat pixel_format_to) { + const u32 bw1 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_from); + const u32 bw2 = VideoCore::Surface::GetDefaultBlockWidth(pixel_format_to); + return (width * bw2 + bw1 - 1) / bw1; + } + + /// Converts a height from a type of surface into another. This helps represent the + /// equivalent value between compressed/non-compressed textures. + static u32 ConvertHeight(u32 height, VideoCore::Surface::PixelFormat pixel_format_from, + VideoCore::Surface::PixelFormat pixel_format_to) { + const u32 bh1 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_from); + const u32 bh2 = VideoCore::Surface::GetDefaultBlockHeight(pixel_format_to); + return (height * bh2 + bh1 - 1) / bh1; + } + + // Finds the maximun possible width between 2 2D layers of different formats + static u32 IntersectWidth(const SurfaceParams& src_params, const SurfaceParams& dst_params, + const u32 src_level, const u32 dst_level) { + const u32 bw1 = src_params.GetDefaultBlockWidth(); + const u32 bw2 = dst_params.GetDefaultBlockWidth(); + const u32 t_src_width = (src_params.GetMipWidth(src_level) * bw2 + bw1 - 1) / bw1; + const u32 t_dst_width = (dst_params.GetMipWidth(dst_level) * bw1 + bw2 - 1) / bw2; + return std::min(t_src_width, t_dst_width); + } + + // Finds the maximun possible height between 2 2D layers of different formats + static u32 IntersectHeight(const SurfaceParams& src_params, const SurfaceParams& dst_params, + const u32 src_level, const u32 dst_level) { + const u32 bh1 = src_params.GetDefaultBlockHeight(); + const u32 bh2 = dst_params.GetDefaultBlockHeight(); + const u32 t_src_height = (src_params.GetMipHeight(src_level) * bh2 + bh1 - 1) / bh1; + const u32 t_dst_height = (dst_params.GetMipHeight(dst_level) * bh1 + bh2 - 1) / bh2; + return std::min(t_src_height, t_dst_height); + } + + bool is_tiled; + bool srgb_conversion; + bool is_layered; + u32 block_width; + u32 block_height; + u32 block_depth; + u32 tile_width_spacing; + u32 width; + u32 height; + u32 depth; + u32 pitch; + u32 num_levels; + u32 emulated_levels; + VideoCore::Surface::PixelFormat pixel_format; + VideoCore::Surface::ComponentType component_type; + VideoCore::Surface::SurfaceType type; + VideoCore::Surface::SurfaceTarget target; + +private: + /// Returns the size of a given mipmap level inside a layer. + std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool uncompressed) const; + + /// Returns the size of all mipmap levels and aligns as needed. + std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const { + return GetLayerSize(as_host_size, uncompressed) * (layer_only ? 1U : depth); + } + + /// Returns the size of a layer + std::size_t GetLayerSize(bool as_host_size, bool uncompressed) const; + + std::size_t GetNumLayers() const { + return is_layered ? depth : 1; + } + + /// Returns true if these parameters are from a layered surface. + bool IsLayered() const; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash { + std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp new file mode 100644 index 0000000000..467696a4cf --- /dev/null +++ b/src/video_core/texture_cache/surface_view.cpp @@ -0,0 +1,23 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/common_types.h" +#include "video_core/texture_cache/surface_view.h" + +namespace VideoCommon { + +std::size_t ViewParams::Hash() const { + return static_cast(base_layer) ^ static_cast(num_layers << 16) ^ + (static_cast(base_level) << 24) ^ + (static_cast(num_levels) << 32) ^ (static_cast(target) << 36); +} + +bool ViewParams::operator==(const ViewParams& rhs) const { + return std::tie(base_layer, num_layers, base_level, num_levels, target) == + std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels, rhs.target); +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h new file mode 100644 index 0000000000..04ca5639bc --- /dev/null +++ b/src/video_core/texture_cache/surface_view.h @@ -0,0 +1,67 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/surface_params.h" + +namespace VideoCommon { + +struct ViewParams { + ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, u32 num_layers, + u32 base_level, u32 num_levels) + : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, + num_levels{num_levels} {} + + std::size_t Hash() const; + + bool operator==(const ViewParams& rhs) const; + + VideoCore::Surface::SurfaceTarget target{}; + u32 base_layer{}; + u32 num_layers{}; + u32 base_level{}; + u32 num_levels{}; + + bool IsLayered() const { + switch (target) { + case VideoCore::Surface::SurfaceTarget::Texture1DArray: + case VideoCore::Surface::SurfaceTarget::Texture2DArray: + case VideoCore::Surface::SurfaceTarget::TextureCubemap: + case VideoCore::Surface::SurfaceTarget::TextureCubeArray: + return true; + default: + return false; + } + } +}; + +class ViewBase { +public: + ViewBase(const ViewParams& params) : params{params} {} + + const ViewParams& GetViewParams() const { + return params; + } + +protected: + ViewParams params; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash { + std::size_t operator()(const VideoCommon::ViewParams& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h new file mode 100644 index 0000000000..c9e72531a5 --- /dev/null +++ b/src/video_core/texture_cache/texture_cache.h @@ -0,0 +1,814 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/assert.h" +#include "common/common_types.h" +#include "common/math_util.h" +#include "core/core.h" +#include "core/memory.h" +#include "core/settings.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/memory_manager.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/surface.h" +#include "video_core/texture_cache/copy_params.h" +#include "video_core/texture_cache/surface_base.h" +#include "video_core/texture_cache/surface_params.h" +#include "video_core/texture_cache/surface_view.h" + +namespace Tegra::Texture { +struct FullTextureInfo; +} + +namespace VideoCore { +class RasterizerInterface; +} + +namespace VideoCommon { + +using VideoCore::Surface::PixelFormat; + +using VideoCore::Surface::SurfaceTarget; +using RenderTargetConfig = Tegra::Engines::Maxwell3D::Regs::RenderTargetConfig; + +template +class TextureCache { + using IntervalMap = boost::icl::interval_map>; + using IntervalType = typename IntervalMap::interval_type; + +public: + void InvalidateRegion(CacheAddr addr, std::size_t size) { + std::lock_guard lock{mutex}; + + for (const auto& surface : GetSurfacesInRegion(addr, size)) { + Unregister(surface); + } + } + + /*** + * `Guard` guarantees that rendertargets don't unregister themselves if the + * collide. Protection is currently only done on 3D slices. + ***/ + void GuardRenderTargets(bool new_guard) { + guard_render_targets = new_guard; + } + + void GuardSamplers(bool new_guard) { + guard_samplers = new_guard; + } + + void FlushRegion(CacheAddr addr, std::size_t size) { + std::lock_guard lock{mutex}; + + auto surfaces = GetSurfacesInRegion(addr, size); + if (surfaces.empty()) { + return; + } + std::sort(surfaces.begin(), surfaces.end(), [](const TSurface& a, const TSurface& b) { + return a->GetModificationTick() < b->GetModificationTick(); + }); + for (const auto& surface : surfaces) { + FlushSurface(surface); + } + } + + TView GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, + const VideoCommon::Shader::Sampler& entry) { + std::lock_guard lock{mutex}; + const auto gpu_addr{config.tic.Address()}; + if (!gpu_addr) { + return {}; + } + const auto params{SurfaceParams::CreateForTexture(system, config, entry)}; + const auto [surface, view] = GetSurface(gpu_addr, params, true, false); + if (guard_samplers) { + sampled_textures.push_back(surface); + } + return view; + } + + bool TextureBarrier() { + const bool any_rt = + std::any_of(sampled_textures.begin(), sampled_textures.end(), + [](const auto& surface) { return surface->IsRenderTarget(); }); + sampled_textures.clear(); + return any_rt; + } + + TView GetDepthBufferSurface(bool preserve_contents) { + std::lock_guard lock{mutex}; + auto& maxwell3d = system.GPU().Maxwell3D(); + + if (!maxwell3d.dirty_flags.zeta_buffer) { + return depth_buffer.view; + } + maxwell3d.dirty_flags.zeta_buffer = false; + + const auto& regs{maxwell3d.regs}; + const auto gpu_addr{regs.zeta.Address()}; + if (!gpu_addr || !regs.zeta_enable) { + SetEmptyDepthBuffer(); + return {}; + } + const auto depth_params{SurfaceParams::CreateForDepthBuffer( + system, regs.zeta_width, regs.zeta_height, regs.zeta.format, + regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, + regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; + auto surface_view = GetSurface(gpu_addr, depth_params, preserve_contents, true); + if (depth_buffer.target) + depth_buffer.target->MarkAsRenderTarget(false); + depth_buffer.target = surface_view.first; + depth_buffer.view = surface_view.second; + if (depth_buffer.target) + depth_buffer.target->MarkAsRenderTarget(true); + return surface_view.second; + } + + TView GetColorBufferSurface(std::size_t index, bool preserve_contents) { + std::lock_guard lock{mutex}; + ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); + auto& maxwell3d = system.GPU().Maxwell3D(); + if (!maxwell3d.dirty_flags.color_buffer[index]) { + return render_targets[index].view; + } + maxwell3d.dirty_flags.color_buffer.reset(index); + + const auto& regs{maxwell3d.regs}; + if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || + regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { + SetEmptyColorBuffer(index); + return {}; + } + + const auto& config{regs.rt[index]}; + const auto gpu_addr{config.Address()}; + if (!gpu_addr) { + SetEmptyColorBuffer(index); + return {}; + } + + auto surface_view = GetSurface(gpu_addr, SurfaceParams::CreateForFramebuffer(system, index), + preserve_contents, true); + if (render_targets[index].target) + render_targets[index].target->MarkAsRenderTarget(false); + render_targets[index].target = surface_view.first; + render_targets[index].view = surface_view.second; + if (render_targets[index].target) + render_targets[index].target->MarkAsRenderTarget(true); + return surface_view.second; + } + + void MarkColorBufferInUse(std::size_t index) { + if (auto& render_target = render_targets[index].target) { + render_target->MarkAsModified(true, Tick()); + } + } + + void MarkDepthBufferInUse() { + if (depth_buffer.target) { + depth_buffer.target->MarkAsModified(true, Tick()); + } + } + + void SetEmptyDepthBuffer() { + if (depth_buffer.target == nullptr) { + return; + } + depth_buffer.target->MarkAsRenderTarget(false); + depth_buffer.target = nullptr; + depth_buffer.view = nullptr; + } + + void SetEmptyColorBuffer(std::size_t index) { + if (render_targets[index].target == nullptr) { + return; + } + render_targets[index].target->MarkAsRenderTarget(false); + render_targets[index].target = nullptr; + render_targets[index].view = nullptr; + } + + void DoFermiCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src_config, + const Tegra::Engines::Fermi2D::Regs::Surface& dst_config, + const Tegra::Engines::Fermi2D::Config& copy_config) { + std::lock_guard lock{mutex}; + std::pair dst_surface = GetFermiSurface(dst_config); + std::pair src_surface = GetFermiSurface(src_config); + ImageBlit(src_surface.second, dst_surface.second, copy_config); + dst_surface.first->MarkAsModified(true, Tick()); + } + + TSurface TryFindFramebufferSurface(const u8* host_ptr) { + const CacheAddr cache_addr = ToCacheAddr(host_ptr); + if (!cache_addr) { + return nullptr; + } + const CacheAddr page = cache_addr >> registry_page_bits; + std::vector& list = registry[page]; + for (auto& surface : list) { + if (surface->GetCacheAddr() == cache_addr) { + return surface; + } + } + return nullptr; + } + + u64 Tick() { + return ++ticks; + } + +protected: + TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) + : system{system}, rasterizer{rasterizer} { + for (std::size_t i = 0; i < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets; i++) { + SetEmptyColorBuffer(i); + } + + SetEmptyDepthBuffer(); + staging_cache.SetSize(2); + + const auto make_siblings = [this](PixelFormat a, PixelFormat b) { + siblings_table[static_cast(a)] = b; + siblings_table[static_cast(b)] = a; + }; + std::fill(siblings_table.begin(), siblings_table.end(), PixelFormat::Invalid); + make_siblings(PixelFormat::Z16, PixelFormat::R16U); + make_siblings(PixelFormat::Z32F, PixelFormat::R32F); + make_siblings(PixelFormat::Z32FS8, PixelFormat::RG32F); + + sampled_textures.reserve(64); + } + + ~TextureCache() = default; + + virtual TSurface CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) = 0; + + virtual void ImageCopy(TSurface& src_surface, TSurface& dst_surface, + const CopyParams& copy_params) = 0; + + virtual void ImageBlit(TView& src_view, TView& dst_view, + const Tegra::Engines::Fermi2D::Config& copy_config) = 0; + + // Depending on the backend, a buffer copy can be slow as it means deoptimizing the texture + // and reading it from a sepparate buffer. + virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0; + + void Register(TSurface surface) { + const GPUVAddr gpu_addr = surface->GetGpuAddr(); + const CacheAddr cache_ptr = ToCacheAddr(system.GPU().MemoryManager().GetPointer(gpu_addr)); + const std::size_t size = surface->GetSizeInBytes(); + const std::optional cpu_addr = + system.GPU().MemoryManager().GpuToCpuAddress(gpu_addr); + if (!cache_ptr || !cpu_addr) { + LOG_CRITICAL(HW_GPU, "Failed to register surface with unmapped gpu_address 0x{:016x}", + gpu_addr); + return; + } + const bool continuous = system.GPU().MemoryManager().IsBlockContinuous(gpu_addr, size); + surface->MarkAsContinuous(continuous); + surface->SetCacheAddr(cache_ptr); + surface->SetCpuAddr(*cpu_addr); + RegisterInnerCache(surface); + surface->MarkAsRegistered(true); + rasterizer.UpdatePagesCachedCount(*cpu_addr, size, 1); + } + + void Unregister(TSurface surface) { + if (guard_render_targets && surface->IsProtected()) { + return; + } + const GPUVAddr gpu_addr = surface->GetGpuAddr(); + const CacheAddr cache_ptr = surface->GetCacheAddr(); + const std::size_t size = surface->GetSizeInBytes(); + const VAddr cpu_addr = surface->GetCpuAddr(); + rasterizer.UpdatePagesCachedCount(cpu_addr, size, -1); + UnregisterInnerCache(surface); + surface->MarkAsRegistered(false); + ReserveSurface(surface->GetSurfaceParams(), surface); + } + + TSurface GetUncachedSurface(const GPUVAddr gpu_addr, const SurfaceParams& params) { + if (const auto surface = TryGetReservedSurface(params); surface) { + surface->SetGpuAddr(gpu_addr); + return surface; + } + // No reserved surface available, create a new one and reserve it + auto new_surface{CreateSurface(gpu_addr, params)}; + return new_surface; + } + + std::pair GetFermiSurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params = SurfaceParams::CreateForFermiCopySurface(config); + const GPUVAddr gpu_addr = config.Address(); + return GetSurface(gpu_addr, params, true, false); + } + + Core::System& system; + +private: + enum class RecycleStrategy : u32 { + Ignore = 0, + Flush = 1, + BufferCopy = 3, + }; + + /** + * `PickStrategy` takes care of selecting a proper strategy to deal with a texture recycle. + * @param overlaps, the overlapping surfaces registered in the cache. + * @param params, the paremeters on the new surface. + * @param gpu_addr, the starting address of the new surface. + * @param untopological, tells the recycler that the texture has no way to match the overlaps + * due to topological reasons. + **/ + RecycleStrategy PickStrategy(std::vector& overlaps, const SurfaceParams& params, + const GPUVAddr gpu_addr, const MatchTopologyResult untopological) { + if (Settings::values.use_accurate_gpu_emulation) { + return RecycleStrategy::Flush; + } + // 3D Textures decision + if (params.block_depth > 1 || params.target == SurfaceTarget::Texture3D) { + return RecycleStrategy::Flush; + } + for (auto s : overlaps) { + const auto& s_params = s->GetSurfaceParams(); + if (s_params.block_depth > 1 || s_params.target == SurfaceTarget::Texture3D) { + return RecycleStrategy::Flush; + } + } + // Untopological decision + if (untopological == MatchTopologyResult::CompressUnmatch) { + return RecycleStrategy::Flush; + } + if (untopological == MatchTopologyResult::FullMatch && !params.is_tiled) { + return RecycleStrategy::Flush; + } + return RecycleStrategy::Ignore; + } + + /** + * `RecycleSurface` es a method we use to decide what to do with textures we can't resolve in + *the cache It has 2 implemented strategies: Ignore and Flush. Ignore just unregisters all the + *overlaps and loads the new texture. Flush, flushes all the overlaps into memory and loads the + *new surface from that data. + * @param overlaps, the overlapping surfaces registered in the cache. + * @param params, the paremeters on the new surface. + * @param gpu_addr, the starting address of the new surface. + * @param preserve_contents, tells if the new surface should be loaded from meory or left blank + * @param untopological, tells the recycler that the texture has no way to match the overlaps + * due to topological reasons. + **/ + std::pair RecycleSurface(std::vector& overlaps, + const SurfaceParams& params, const GPUVAddr gpu_addr, + const bool preserve_contents, + const MatchTopologyResult untopological) { + const bool do_load = preserve_contents && Settings::values.use_accurate_gpu_emulation; + for (auto& surface : overlaps) { + Unregister(surface); + } + switch (PickStrategy(overlaps, params, gpu_addr, untopological)) { + case RecycleStrategy::Ignore: { + return InitializeSurface(gpu_addr, params, do_load); + } + case RecycleStrategy::Flush: { + std::sort(overlaps.begin(), overlaps.end(), + [](const TSurface& a, const TSurface& b) -> bool { + return a->GetModificationTick() < b->GetModificationTick(); + }); + for (auto& surface : overlaps) { + FlushSurface(surface); + } + return InitializeSurface(gpu_addr, params, preserve_contents); + } + case RecycleStrategy::BufferCopy: { + auto new_surface = GetUncachedSurface(gpu_addr, params); + BufferCopy(overlaps[0], new_surface); + return {new_surface, new_surface->GetMainView()}; + } + default: { + UNIMPLEMENTED_MSG("Unimplemented Texture Cache Recycling Strategy!"); + return InitializeSurface(gpu_addr, params, do_load); + } + } + } + + /** + * `RebuildSurface` this method takes a single surface and recreates into another that + * may differ in format, target or width alingment. + * @param current_surface, the registered surface in the cache which we want to convert. + * @param params, the new surface params which we'll use to recreate the surface. + **/ + std::pair RebuildSurface(TSurface current_surface, const SurfaceParams& params, + bool is_render) { + const auto gpu_addr = current_surface->GetGpuAddr(); + const auto& cr_params = current_surface->GetSurfaceParams(); + TSurface new_surface; + if (cr_params.pixel_format != params.pixel_format && !is_render && + GetSiblingFormat(cr_params.pixel_format) == params.pixel_format) { + SurfaceParams new_params = params; + new_params.pixel_format = cr_params.pixel_format; + new_params.component_type = cr_params.component_type; + new_params.type = cr_params.type; + new_surface = GetUncachedSurface(gpu_addr, new_params); + } else { + new_surface = GetUncachedSurface(gpu_addr, params); + } + const auto& final_params = new_surface->GetSurfaceParams(); + if (cr_params.type != final_params.type || + (cr_params.component_type != final_params.component_type)) { + BufferCopy(current_surface, new_surface); + } else { + std::vector bricks = current_surface->BreakDown(final_params); + for (auto& brick : bricks) { + ImageCopy(current_surface, new_surface, brick); + } + } + Unregister(current_surface); + Register(new_surface); + new_surface->MarkAsModified(current_surface->IsModified(), Tick()); + return {new_surface, new_surface->GetMainView()}; + } + + /** + * `ManageStructuralMatch` this method takes a single surface and checks with the new surface's + * params if it's an exact match, we return the main view of the registered surface. If it's + * formats don't match, we rebuild the surface. We call this last method a `Mirage`. If formats + * match but the targets don't, we create an overview View of the registered surface. + * @param current_surface, the registered surface in the cache which we want to convert. + * @param params, the new surface params which we want to check. + **/ + std::pair ManageStructuralMatch(TSurface current_surface, + const SurfaceParams& params, bool is_render) { + const bool is_mirage = !current_surface->MatchFormat(params.pixel_format); + const bool matches_target = current_surface->MatchTarget(params.target); + const auto match_check = [&]() -> std::pair { + if (matches_target) { + return {current_surface, current_surface->GetMainView()}; + } + return {current_surface, current_surface->EmplaceOverview(params)}; + }; + if (!is_mirage) { + return match_check(); + } + if (!is_render && GetSiblingFormat(current_surface->GetFormat()) == params.pixel_format) { + return match_check(); + } + return RebuildSurface(current_surface, params, is_render); + } + + /** + * `TryReconstructSurface` unlike `RebuildSurface` where we know the registered surface + * matches the candidate in some way, we got no guarantess here. We try to see if the overlaps + * are sublayers/mipmaps of the new surface, if they all match we end up recreating a surface + * for them, else we return nothing. + * @param overlaps, the overlapping surfaces registered in the cache. + * @param params, the paremeters on the new surface. + * @param gpu_addr, the starting address of the new surface. + **/ + std::optional> TryReconstructSurface(std::vector& overlaps, + const SurfaceParams& params, + const GPUVAddr gpu_addr) { + if (params.target == SurfaceTarget::Texture3D) { + return {}; + } + bool modified = false; + TSurface new_surface = GetUncachedSurface(gpu_addr, params); + u32 passed_tests = 0; + for (auto& surface : overlaps) { + const SurfaceParams& src_params = surface->GetSurfaceParams(); + if (src_params.is_layered || src_params.num_levels > 1) { + // We send this cases to recycle as they are more complex to handle + return {}; + } + const std::size_t candidate_size = surface->GetSizeInBytes(); + auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())}; + if (!mipmap_layer) { + continue; + } + const auto [layer, mipmap] = *mipmap_layer; + if (new_surface->GetMipmapSize(mipmap) != candidate_size) { + continue; + } + modified |= surface->IsModified(); + // Now we got all the data set up + const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap); + const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap); + const CopyParams copy_params(0, 0, 0, 0, 0, layer, 0, mipmap, width, height, 1); + passed_tests++; + ImageCopy(surface, new_surface, copy_params); + } + if (passed_tests == 0) { + return {}; + // In Accurate GPU all tests should pass, else we recycle + } else if (Settings::values.use_accurate_gpu_emulation && passed_tests != overlaps.size()) { + return {}; + } + for (auto surface : overlaps) { + Unregister(surface); + } + new_surface->MarkAsModified(modified, Tick()); + Register(new_surface); + return {{new_surface, new_surface->GetMainView()}}; + } + + /** + * `GetSurface` gets the starting address and parameters of a candidate surface and tries + * to find a matching surface within the cache. This is done in 3 big steps. The first is to + * check the 1st Level Cache in order to find an exact match, if we fail, we move to step 2. + * Step 2 is checking if there are any overlaps at all, if none, we just load the texture from + * memory else we move to step 3. Step 3 consists on figuring the relationship between the + * candidate texture and the overlaps. We divide the scenarios depending if there's 1 or many + * overlaps. If there's many, we just try to reconstruct a new surface out of them based on the + * candidate's parameters, if we fail, we recycle. When there's only 1 overlap then we have to + * check if the candidate is a view (layer/mipmap) of the overlap or if the registered surface + * is a mipmap/layer of the candidate. In this last case we reconstruct a new surface. + * @param gpu_addr, the starting address of the candidate surface. + * @param params, the paremeters on the candidate surface. + * @param preserve_contents, tells if the new surface should be loaded from meory or left blank. + **/ + std::pair GetSurface(const GPUVAddr gpu_addr, const SurfaceParams& params, + bool preserve_contents, bool is_render) { + const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)}; + const auto cache_addr{ToCacheAddr(host_ptr)}; + + // Step 0: guarantee a valid surface + if (!cache_addr) { + // Return a null surface if it's invalid + SurfaceParams new_params = params; + new_params.width = 1; + new_params.height = 1; + new_params.depth = 1; + new_params.block_height = 0; + new_params.block_depth = 0; + return InitializeSurface(gpu_addr, new_params, false); + } + + // Step 1 + // Check Level 1 Cache for a fast structural match. If candidate surface + // matches at certain level we are pretty much done. + if (const auto iter = l1_cache.find(cache_addr); iter != l1_cache.end()) { + TSurface& current_surface = iter->second; + const auto topological_result = current_surface->MatchesTopology(params); + if (topological_result != MatchTopologyResult::FullMatch) { + std::vector overlaps{current_surface}; + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + topological_result); + } + const auto struct_result = current_surface->MatchesStructure(params); + if (struct_result != MatchStructureResult::None && + (params.target != SurfaceTarget::Texture3D || + current_surface->MatchTarget(params.target))) { + if (struct_result == MatchStructureResult::FullMatch) { + return ManageStructuralMatch(current_surface, params, is_render); + } else { + return RebuildSurface(current_surface, params, is_render); + } + } + } + + // Step 2 + // Obtain all possible overlaps in the memory region + const std::size_t candidate_size = params.GetGuestSizeInBytes(); + auto overlaps{GetSurfacesInRegion(cache_addr, candidate_size)}; + + // If none are found, we are done. we just load the surface and create it. + if (overlaps.empty()) { + return InitializeSurface(gpu_addr, params, preserve_contents); + } + + // Step 3 + // Now we need to figure the relationship between the texture and its overlaps + // we do a topological test to ensure we can find some relationship. If it fails + // inmediatly recycle the texture + for (const auto& surface : overlaps) { + const auto topological_result = surface->MatchesTopology(params); + if (topological_result != MatchTopologyResult::FullMatch) { + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + topological_result); + } + } + + // Split cases between 1 overlap or many. + if (overlaps.size() == 1) { + TSurface current_surface = overlaps[0]; + // First check if the surface is within the overlap. If not, it means + // two things either the candidate surface is a supertexture of the overlap + // or they don't match in any known way. + if (!current_surface->IsInside(gpu_addr, gpu_addr + candidate_size)) { + if (current_surface->GetGpuAddr() == gpu_addr) { + std::optional> view = + TryReconstructSurface(overlaps, params, gpu_addr); + if (view) { + return *view; + } + } + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + // Now we check if the candidate is a mipmap/layer of the overlap + std::optional view = + current_surface->EmplaceView(params, gpu_addr, candidate_size); + if (view) { + const bool is_mirage = !current_surface->MatchFormat(params.pixel_format); + if (is_mirage) { + // On a mirage view, we need to recreate the surface under this new view + // and then obtain a view again. + SurfaceParams new_params = current_surface->GetSurfaceParams(); + const u32 wh = SurfaceParams::ConvertWidth( + new_params.width, new_params.pixel_format, params.pixel_format); + const u32 hh = SurfaceParams::ConvertHeight( + new_params.height, new_params.pixel_format, params.pixel_format); + new_params.width = wh; + new_params.height = hh; + new_params.pixel_format = params.pixel_format; + std::pair pair = + RebuildSurface(current_surface, new_params, is_render); + std::optional mirage_view = + pair.first->EmplaceView(params, gpu_addr, candidate_size); + if (mirage_view) + return {pair.first, *mirage_view}; + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + return {current_surface, *view}; + } + // The next case is unsafe, so if we r in accurate GPU, just skip it + if (Settings::values.use_accurate_gpu_emulation) { + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + // This is the case the texture is a part of the parent. + if (current_surface->MatchesSubTexture(params, gpu_addr)) { + return RebuildSurface(current_surface, params, is_render); + } + } else { + // If there are many overlaps, odds are they are subtextures of the candidate + // surface. We try to construct a new surface based on the candidate parameters, + // using the overlaps. If a single overlap fails, this will fail. + std::optional> view = + TryReconstructSurface(overlaps, params, gpu_addr); + if (view) { + return *view; + } + } + // We failed all the tests, recycle the overlaps into a new texture. + return RecycleSurface(overlaps, params, gpu_addr, preserve_contents, + MatchTopologyResult::FullMatch); + } + + std::pair InitializeSurface(GPUVAddr gpu_addr, const SurfaceParams& params, + bool preserve_contents) { + auto new_surface{GetUncachedSurface(gpu_addr, params)}; + Register(new_surface); + if (preserve_contents) { + LoadSurface(new_surface); + } + return {new_surface, new_surface->GetMainView()}; + } + + void LoadSurface(const TSurface& surface) { + staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); + surface->LoadBuffer(system.GPU().MemoryManager(), staging_cache); + surface->UploadTexture(staging_cache.GetBuffer(0)); + surface->MarkAsModified(false, Tick()); + } + + void FlushSurface(const TSurface& surface) { + if (!surface->IsModified()) { + return; + } + staging_cache.GetBuffer(0).resize(surface->GetHostSizeInBytes()); + surface->DownloadTexture(staging_cache.GetBuffer(0)); + surface->FlushBuffer(system.GPU().MemoryManager(), staging_cache); + surface->MarkAsModified(false, Tick()); + } + + void RegisterInnerCache(TSurface& surface) { + const CacheAddr cache_addr = surface->GetCacheAddr(); + CacheAddr start = cache_addr >> registry_page_bits; + const CacheAddr end = (surface->GetCacheAddrEnd() - 1) >> registry_page_bits; + l1_cache[cache_addr] = surface; + while (start <= end) { + registry[start].push_back(surface); + start++; + } + } + + void UnregisterInnerCache(TSurface& surface) { + const CacheAddr cache_addr = surface->GetCacheAddr(); + CacheAddr start = cache_addr >> registry_page_bits; + const CacheAddr end = (surface->GetCacheAddrEnd() - 1) >> registry_page_bits; + l1_cache.erase(cache_addr); + while (start <= end) { + auto& reg{registry[start]}; + reg.erase(std::find(reg.begin(), reg.end(), surface)); + start++; + } + } + + std::vector GetSurfacesInRegion(const CacheAddr cache_addr, const std::size_t size) { + if (size == 0) { + return {}; + } + const CacheAddr cache_addr_end = cache_addr + size; + CacheAddr start = cache_addr >> registry_page_bits; + const CacheAddr end = (cache_addr_end - 1) >> registry_page_bits; + std::vector surfaces; + while (start <= end) { + std::vector& list = registry[start]; + for (auto& surface : list) { + if (!surface->IsPicked() && surface->Overlaps(cache_addr, cache_addr_end)) { + surface->MarkAsPicked(true); + surfaces.push_back(surface); + } + } + start++; + } + for (auto& surface : surfaces) { + surface->MarkAsPicked(false); + } + return surfaces; + } + + void ReserveSurface(const SurfaceParams& params, TSurface surface) { + surface_reserve[params].push_back(std::move(surface)); + } + + TSurface TryGetReservedSurface(const SurfaceParams& params) { + auto search{surface_reserve.find(params)}; + if (search == surface_reserve.end()) { + return {}; + } + for (auto& surface : search->second) { + if (!surface->IsRegistered()) { + return surface; + } + } + return {}; + } + + constexpr PixelFormat GetSiblingFormat(PixelFormat format) const { + return siblings_table[static_cast(format)]; + } + + struct FramebufferTargetInfo { + TSurface target; + TView view; + }; + + VideoCore::RasterizerInterface& rasterizer; + + u64 ticks{}; + + // Guards the cache for protection conflicts. + bool guard_render_targets{}; + bool guard_samplers{}; + + // The siblings table is for formats that can inter exchange with one another + // without causing issues. This is only valid when a conflict occurs on a non + // rendering use. + std::array(PixelFormat::Max)> siblings_table; + + // The internal Cache is different for the Texture Cache. It's based on buckets + // of 1MB. This fits better for the purpose of this cache as textures are normaly + // large in size. + static constexpr u64 registry_page_bits{20}; + static constexpr u64 registry_page_size{1 << registry_page_bits}; + std::unordered_map> registry; + + // The L1 Cache is used for fast texture lookup before checking the overlaps + // This avoids calculating size and other stuffs. + std::unordered_map l1_cache; + + /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have + /// previously been used. This is to prevent surfaces from being constantly created and + /// destroyed when used with different surface parameters. + std::unordered_map> surface_reserve; + std::array + render_targets; + FramebufferTargetInfo depth_buffer; + + std::vector sampled_textures; + + StagingCache staging_cache; + std::recursive_mutex mutex; +}; + +} // namespace VideoCommon diff --git a/src/video_core/textures/convert.cpp b/src/video_core/textures/convert.cpp index 82050bd512..f3efa7eb05 100644 --- a/src/video_core/textures/convert.cpp +++ b/src/video_core/textures/convert.cpp @@ -62,19 +62,19 @@ static void ConvertZ24S8ToS8Z24(u8* data, u32 width, u32 height) { SwapS8Z24ToZ24S8(data, width, height); } -void ConvertFromGuestToHost(u8* data, PixelFormat pixel_format, u32 width, u32 height, u32 depth, - bool convert_astc, bool convert_s8z24) { +void ConvertFromGuestToHost(u8* in_data, u8* out_data, PixelFormat pixel_format, u32 width, + u32 height, u32 depth, bool convert_astc, bool convert_s8z24) { if (convert_astc && IsPixelFormatASTC(pixel_format)) { // Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC. u32 block_width{}; u32 block_height{}; std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format); - const std::vector rgba8_data = - Tegra::Texture::ASTC::Decompress(data, width, height, depth, block_width, block_height); - std::copy(rgba8_data.begin(), rgba8_data.end(), data); + const std::vector rgba8_data = Tegra::Texture::ASTC::Decompress( + in_data, width, height, depth, block_width, block_height); + std::copy(rgba8_data.begin(), rgba8_data.end(), out_data); } else if (convert_s8z24 && pixel_format == PixelFormat::S8Z24) { - Tegra::Texture::ConvertS8Z24ToZ24S8(data, width, height); + Tegra::Texture::ConvertS8Z24ToZ24S8(in_data, width, height); } } @@ -90,4 +90,4 @@ void ConvertFromHostToGuest(u8* data, PixelFormat pixel_format, u32 width, u32 h } } -} // namespace Tegra::Texture \ No newline at end of file +} // namespace Tegra::Texture diff --git a/src/video_core/textures/convert.h b/src/video_core/textures/convert.h index 12542e71c6..d5d6c77bb2 100644 --- a/src/video_core/textures/convert.h +++ b/src/video_core/textures/convert.h @@ -12,10 +12,11 @@ enum class PixelFormat; namespace Tegra::Texture { -void ConvertFromGuestToHost(u8* data, VideoCore::Surface::PixelFormat pixel_format, u32 width, - u32 height, u32 depth, bool convert_astc, bool convert_s8z24); +void ConvertFromGuestToHost(u8* in_data, u8* out_data, VideoCore::Surface::PixelFormat pixel_format, + u32 width, u32 height, u32 depth, bool convert_astc, + bool convert_s8z24); void ConvertFromHostToGuest(u8* data, VideoCore::Surface::PixelFormat pixel_format, u32 width, u32 height, u32 depth, bool convert_astc, bool convert_s8z24); -} // namespace Tegra::Texture \ No newline at end of file +} // namespace Tegra::Texture diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 2178053865..7e82959440 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -36,10 +36,16 @@ struct alignas(64) SwizzleTable { std::array, N> values{}; }; -constexpr u32 gob_size_x = 64; -constexpr u32 gob_size_y = 8; -constexpr u32 gob_size_z = 1; -constexpr u32 gob_size = gob_size_x * gob_size_y * gob_size_z; +constexpr u32 gob_size_x_shift = 6; +constexpr u32 gob_size_y_shift = 3; +constexpr u32 gob_size_z_shift = 0; +constexpr u32 gob_size_shift = gob_size_x_shift + gob_size_y_shift + gob_size_z_shift; + +constexpr u32 gob_size_x = 1U << gob_size_x_shift; +constexpr u32 gob_size_y = 1U << gob_size_y_shift; +constexpr u32 gob_size_z = 1U << gob_size_z_shift; +constexpr u32 gob_size = 1U << gob_size_shift; + constexpr u32 fast_swizzle_align = 16; constexpr auto legacy_swizzle_table = SwizzleTable(); @@ -171,14 +177,16 @@ void SwizzledData(u8* const swizzled_data, u8* const unswizzled_data, const bool void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel, u32 out_bytes_per_pixel, u8* const swizzled_data, u8* const unswizzled_data, bool unswizzle, u32 block_height, u32 block_depth, u32 width_spacing) { + const u32 block_height_size{1U << block_height}; + const u32 block_depth_size{1U << block_depth}; if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % fast_swizzle_align == 0) { SwizzledData(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth, - width_spacing); + bytes_per_pixel, out_bytes_per_pixel, block_height_size, + block_depth_size, width_spacing); } else { SwizzledData(swizzled_data, unswizzled_data, unswizzle, width, height, depth, - bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth, - width_spacing); + bytes_per_pixel, out_bytes_per_pixel, block_height_size, + block_depth_size, width_spacing); } } @@ -248,7 +256,9 @@ std::vector UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, } void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) { + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, + u32 block_height_bit) { + const u32 block_height = 1U << block_height_bit; const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) / gob_size_x}; for (u32 line = 0; line < subrect_height; ++line) { @@ -269,8 +279,9 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 } void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, - u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height, - u32 offset_x, u32 offset_y) { + u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, + u32 block_height_bit, u32 offset_x, u32 offset_y) { + const u32 block_height = 1U << block_height_bit; for (u32 line = 0; line < subrect_height; ++line) { const u32 y2 = line + offset_y; const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height + @@ -289,8 +300,9 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 } void SwizzleKepler(const u32 width, const u32 height, const u32 dst_x, const u32 dst_y, - const u32 block_height, const std::size_t copy_size, const u8* source_data, + const u32 block_height_bit, const std::size_t copy_size, const u8* source_data, u8* swizzle_data) { + const u32 block_height = 1U << block_height_bit; const u32 image_width_in_gobs{(width + gob_size_x - 1) / gob_size_x}; std::size_t count = 0; for (std::size_t y = dst_y; y < height && count < copy_size; ++y) { @@ -356,9 +368,9 @@ std::vector DecodeTexture(const std::vector& texture_data, TextureFormat std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height, u32 block_depth) { if (tiled) { - const u32 aligned_width = Common::AlignUp(width * bytes_per_pixel, gob_size_x); - const u32 aligned_height = Common::AlignUp(height, gob_size_y * block_height); - const u32 aligned_depth = Common::AlignUp(depth, gob_size_z * block_depth); + const u32 aligned_width = Common::AlignBits(width * bytes_per_pixel, gob_size_x_shift); + const u32 aligned_height = Common::AlignBits(height, gob_size_y_shift + block_height); + const u32 aligned_depth = Common::AlignBits(depth, gob_size_z_shift + block_depth); return aligned_width * aligned_height * aligned_depth; } else { return width * height * depth * bytes_per_pixel; diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index e072d84017..eaec9b5a5b 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -12,8 +12,8 @@ namespace Tegra::Texture { // GOBSize constant. Calculated by 64 bytes in x multiplied by 8 y coords, represents // an small rect of (64/bytes_per_pixel)X8. -inline std::size_t GetGOBSize() { - return 512; +inline std::size_t GetGOBSizeShift() { + return 9; } /// Unswizzles a swizzled texture without changing its format. diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h index 219bfd5598..e3be018b97 100644 --- a/src/video_core/textures/texture.h +++ b/src/video_core/textures/texture.h @@ -52,9 +52,9 @@ enum class TextureFormat : u32 { DXT45 = 0x26, DXN1 = 0x27, DXN2 = 0x28, - Z24S8 = 0x29, + S8Z24 = 0x29, X8Z24 = 0x2a, - S8Z24 = 0x2b, + Z24S8 = 0x2b, X4V4Z24__COV4R4V = 0x2c, X4V4Z24__COV8R8V = 0x2d, V8Z24__COV4R12V = 0x2e, @@ -172,12 +172,16 @@ struct TICEntry { BitField<26, 1, u32> use_header_opt_control; BitField<27, 1, u32> depth_texture; BitField<28, 4, u32> max_mip_level; + + BitField<0, 16, u32> buffer_high_width_minus_one; }; union { BitField<0, 16, u32> width_minus_1; BitField<22, 1, u32> srgb_conversion; BitField<23, 4, TextureType> texture_type; BitField<29, 3, u32> border_size; + + BitField<0, 16, u32> buffer_low_width_minus_one; }; union { BitField<0, 16, u32> height_minus_1; @@ -206,7 +210,10 @@ struct TICEntry { } u32 Width() const { - return width_minus_1 + 1; + if (header_version != TICHeaderVersion::OneDBuffer) { + return width_minus_1 + 1; + } + return (buffer_high_width_minus_one << 16) | buffer_low_width_minus_one; } u32 Height() const { @@ -219,20 +226,17 @@ struct TICEntry { u32 BlockWidth() const { ASSERT(IsTiled()); - // The block height is stored in log2 format. - return 1 << block_width; + return block_width; } u32 BlockHeight() const { ASSERT(IsTiled()); - // The block height is stored in log2 format. - return 1 << block_height; + return block_height; } u32 BlockDepth() const { ASSERT(IsTiled()); - // The block height is stored in log2 format. - return 1 << block_depth; + return block_depth; } bool IsTiled() const { @@ -240,6 +244,15 @@ struct TICEntry { header_version == TICHeaderVersion::BlockLinearColorKey; } + bool IsLineal() const { + return header_version == TICHeaderVersion::Pitch || + header_version == TICHeaderVersion::PitchColorKey; + } + + bool IsBuffer() const { + return header_version == TICHeaderVersion::OneDBuffer; + } + bool IsSrgbConversionEnabled() const { return srgb_conversion != 0; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 47e46f574c..ae21f47535 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -750,6 +750,9 @@ void GMainWindow::OnDisplayTitleBars(bool show) { QStringList GMainWindow::GetUnsupportedGLExtensions() { QStringList unsupported_ext; + if (!GLAD_GL_ARB_buffer_storage) { + unsupported_ext.append(QStringLiteral("ARB_buffer_storage")); + } if (!GLAD_GL_ARB_direct_state_access) { unsupported_ext.append(QStringLiteral("ARB_direct_state_access")); } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index e2d3df1802..f91b071bf5 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -52,6 +52,10 @@ private: bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { std::vector unsupported_ext; + if (!GLAD_GL_ARB_buffer_storage) + unsupported_ext.push_back("ARB_buffer_storage"); + if (!GLAD_GL_ARB_direct_state_access) + unsupported_ext.push_back("ARB_direct_state_access"); if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev) unsupported_ext.push_back("ARB_vertex_type_10f_11f_11f_rev"); if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)