diff options
Diffstat (limited to 'src/video_core')
22 files changed, 1137 insertions, 265 deletions
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index 597b279b9..74e44c7fe 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -47,9 +47,12 @@ void Fermi2D::HandleSurfaceCopy() { u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format); if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) { - // TODO(bunnei): The below implementation currently will not get hit, as - // AccelerateSurfaceCopy tries to always copy and will always return success. This should be - // changed once we properly support flushing. + rasterizer.FlushRegion(source_cpu, src_bytes_per_pixel * regs.src.width * regs.src.height); + // We have to invalidate the destination region to evict any outdated surfaces from the + // cache. We do this before actually writing the new data because the destination address + // might contain a dirty surface that will have to be written back to memory. + rasterizer.InvalidateRegion(dest_cpu, + dst_bytes_per_pixel * regs.dst.width * regs.dst.height); if (regs.src.linear == regs.dst.linear) { // If the input layout and the output layout are the same, just perform a raw copy. diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp index 66ae6332d..585290d9f 100644 --- a/src/video_core/engines/kepler_memory.cpp +++ b/src/video_core/engines/kepler_memory.cpp @@ -5,10 +5,14 @@ #include "common/logging/log.h" #include "core/memory.h" #include "video_core/engines/kepler_memory.h" +#include "video_core/rasterizer_interface.h" namespace Tegra::Engines { -KeplerMemory::KeplerMemory(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +KeplerMemory::KeplerMemory(VideoCore::RasterizerInterface& rasterizer, + MemoryManager& memory_manager) + : memory_manager(memory_manager), rasterizer{rasterizer} {} + KeplerMemory::~KeplerMemory() = default; void KeplerMemory::WriteReg(u32 method, u32 value) { @@ -37,6 +41,11 @@ void KeplerMemory::ProcessData(u32 data) { VAddr dest_address = *memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32)); + // We have to invalidate the destination region to evict any outdated surfaces from the cache. + // We do this before actually writing the new data because the destination address might contain + // a dirty surface that will have to be written back to memory. + rasterizer.InvalidateRegion(dest_address, sizeof(u32)); + Memory::Write32(dest_address, data); state.write_offset++; diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h index b0d0078cf..bf4a13cff 100644 --- a/src/video_core/engines/kepler_memory.h +++ b/src/video_core/engines/kepler_memory.h @@ -11,6 +11,10 @@ #include "common/common_types.h" #include "video_core/memory_manager.h" +namespace VideoCore { +class RasterizerInterface; +} + namespace Tegra::Engines { #define KEPLERMEMORY_REG_INDEX(field_name) \ @@ -18,7 +22,7 @@ namespace Tegra::Engines { class KeplerMemory final { public: - KeplerMemory(MemoryManager& memory_manager); + KeplerMemory(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager); ~KeplerMemory(); /// Write the value to the register identified by method. @@ -72,6 +76,7 @@ public: private: MemoryManager& memory_manager; + VideoCore::RasterizerInterface& rasterizer; void ProcessData(u32 data); }; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index c8d1b6478..c8af1c6b6 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -448,7 +448,10 @@ public: BitField<8, 3, u32> block_depth; BitField<12, 1, InvMemoryLayout> type; } memory_layout; - u32 array_mode; + union { + BitField<0, 16, u32> array_mode; + BitField<16, 1, u32> volume; + }; u32 layer_stride; u32 base_layer; INSERT_PADDING_WORDS(7); diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index bf2a21bb6..103cd110e 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -4,12 +4,14 @@ #include "core/memory.h" #include "video_core/engines/maxwell_dma.h" +#include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" namespace Tegra { namespace Engines { -MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager) : memory_manager(memory_manager) {} +MaxwellDMA::MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) + : memory_manager(memory_manager), rasterizer{rasterizer} {} void MaxwellDMA::WriteReg(u32 method, u32 value) { ASSERT_MSG(method < Regs::NUM_REGS, @@ -44,38 +46,79 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.exec.query_mode == Regs::QueryMode::None); ASSERT(regs.exec.query_intr == Regs::QueryIntr::None); ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2); - ASSERT(regs.src_params.pos_x == 0); - ASSERT(regs.src_params.pos_y == 0); ASSERT(regs.dst_params.pos_x == 0); ASSERT(regs.dst_params.pos_y == 0); - if (regs.exec.is_dst_linear == regs.exec.is_src_linear) { - std::size_t copy_size = regs.x_count; + if (!regs.exec.is_dst_linear && !regs.exec.is_src_linear) { + // If both the source and the destination are in block layout, assert. + UNREACHABLE_MSG("Tiled->Tiled DMA transfers are not yet implemented"); + return; + } + if (regs.exec.is_dst_linear && regs.exec.is_src_linear) { // When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D - // buffer of length `x_count`, otherwise we copy a 2D buffer of size (x_count, y_count). - if (regs.exec.enable_2d) { - copy_size = copy_size * regs.y_count; + // buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count, + // y_count). + if (!regs.exec.enable_2d) { + Memory::CopyBlock(dest_cpu, source_cpu, regs.x_count); + return; } - Memory::CopyBlock(dest_cpu, source_cpu, copy_size); + // If both the source and the destination are in linear layout, perform a line-by-line + // copy. We're going to take a subrect of size (x_count, y_count) from the source + // rectangle. There is no need to manually flush/invalidate the regions because + // CopyBlock does that for us. + for (u32 line = 0; line < regs.y_count; ++line) { + const VAddr source_line = source_cpu + line * regs.src_pitch; + const VAddr dest_line = dest_cpu + line * regs.dst_pitch; + Memory::CopyBlock(dest_line, source_line, regs.x_count); + } return; } ASSERT(regs.exec.enable_2d == 1); + + std::size_t copy_size = regs.x_count * regs.y_count; + + const auto FlushAndInvalidate = [&](u32 src_size, u32 dst_size) { + // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated + // copying. + rasterizer.FlushRegion(source_cpu, src_size); + + // We have to invalidate the destination region to evict any outdated surfaces from the + // cache. We do this before actually writing the new data because the destination address + // might contain a dirty surface that will have to be written back to memory. + rasterizer.InvalidateRegion(dest_cpu, dst_size); + }; + u8* src_buffer = Memory::GetPointer(source_cpu); u8* dst_buffer = Memory::GetPointer(dest_cpu); if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { + ASSERT(regs.src_params.size_z == 1); // If the input is tiled and the output is linear, deswizzle the input and copy it over. - Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, - regs.src_params.size_z, 1, 1, src_buffer, dst_buffer, true, - regs.src_params.BlockHeight(), regs.src_params.BlockDepth()); + + u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; + + FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y, + copy_size * src_bytes_per_pixel); + + Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch, + regs.src_params.size_x, src_bytes_per_pixel, source_cpu, dest_cpu, + regs.src_params.BlockHeight(), regs.src_params.pos_x, + regs.src_params.pos_y); } else { + ASSERT(regs.dst_params.size_z == 1); + ASSERT(regs.src_pitch == regs.x_count); + + u32 src_bpp = regs.src_pitch / regs.x_count; + + FlushAndInvalidate(regs.src_pitch * regs.y_count, + regs.dst_params.size_x * regs.dst_params.size_y * src_bpp); + // If the input is linear and the output is tiled, swizzle the input and copy it over. - Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, - regs.dst_params.size_z, 1, 1, dst_buffer, src_buffer, false, - regs.dst_params.BlockHeight(), regs.dst_params.BlockDepth()); + Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x, + src_bpp, dest_cpu, source_cpu, regs.dst_params.BlockHeight()); } } diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h index df19e02e2..5f3704f05 100644 --- a/src/video_core/engines/maxwell_dma.h +++ b/src/video_core/engines/maxwell_dma.h @@ -12,11 +12,15 @@ #include "video_core/gpu.h" #include "video_core/memory_manager.h" +namespace VideoCore { +class RasterizerInterface; +} + namespace Tegra::Engines { class MaxwellDMA final { public: - explicit MaxwellDMA(MemoryManager& memory_manager); + explicit MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager); ~MaxwellDMA() = default; /// Write the value to the register identified by method. @@ -133,6 +137,8 @@ public: MemoryManager& memory_manager; private: + VideoCore::RasterizerInterface& rasterizer; + /// Performs the copy from the source buffer to the destination buffer as configured in the /// registers. void HandleCopy(); diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index 8c8d65769..ac50bb622 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -335,6 +335,26 @@ enum class IsberdMode : u64 { enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 }; +enum class HalfType : u64 { + H0_H1 = 0, + F32 = 1, + H0_H0 = 2, + H1_H1 = 3, +}; + +enum class HalfMerge : u64 { + H0_H1 = 0, + F32 = 1, + Mrg_H0 = 2, + Mrg_H1 = 3, +}; + +enum class HalfPrecision : u64 { + None = 0, + FTZ = 1, + FMZ = 2, +}; + enum class IpaInterpMode : u64 { Linear = 0, Perspective = 1, @@ -544,6 +564,10 @@ union Instruction { } fmul; union { + BitField<55, 1, u64> saturate; + } fmul32; + + union { BitField<48, 1, u64> is_signed; } shift; @@ -554,6 +578,70 @@ union Instruction { } alu_integer; union { + BitField<39, 1, u64> ftz; + BitField<32, 1, u64> saturate; + BitField<49, 2, HalfMerge> merge; + + BitField<43, 1, u64> negate_a; + BitField<44, 1, u64> abs_a; + BitField<47, 2, HalfType> type_a; + + BitField<31, 1, u64> negate_b; + BitField<30, 1, u64> abs_b; + BitField<47, 2, HalfType> type_b; + + BitField<35, 2, HalfType> type_c; + } alu_half; + + union { + BitField<39, 2, HalfPrecision> precision; + BitField<39, 1, u64> ftz; + BitField<52, 1, u64> saturate; + BitField<49, 2, HalfMerge> merge; + + BitField<43, 1, u64> negate_a; + BitField<44, 1, u64> abs_a; + BitField<47, 2, HalfType> type_a; + } alu_half_imm; + + union { + BitField<29, 1, u64> first_negate; + BitField<20, 9, u64> first; + + BitField<56, 1, u64> second_negate; + BitField<30, 9, u64> second; + + u32 PackImmediates() const { + // Immediates are half floats shifted. + constexpr u32 imm_shift = 6; + return static_cast<u32>((first << imm_shift) | (second << (16 + imm_shift))); + } + } half_imm; + + union { + union { + BitField<37, 2, HalfPrecision> precision; + BitField<32, 1, u64> saturate; + + BitField<30, 1, u64> negate_c; + BitField<35, 2, HalfType> type_c; + } rr; + + BitField<57, 2, HalfPrecision> precision; + BitField<52, 1, u64> saturate; + + BitField<49, 2, HalfMerge> merge; + + BitField<47, 2, HalfType> type_a; + + BitField<56, 1, u64> negate_b; + BitField<28, 2, HalfType> type_b; + + BitField<51, 1, u64> negate_c; + BitField<53, 2, HalfType> type_reg39; + } hfma2; + + union { BitField<40, 1, u64> invert; } popc; @@ -669,7 +757,6 @@ union Instruction { BitField<45, 2, PredOperation> op; BitField<47, 1, u64> ftz; BitField<48, 4, PredCondition> cond; - BitField<56, 1, u64> neg_b; } fsetp; union { @@ -717,6 +804,23 @@ union Instruction { } csetp; union { + BitField<35, 4, PredCondition> cond; + BitField<49, 1, u64> h_and; + BitField<6, 1, u64> ftz; + BitField<45, 2, PredOperation> op; + BitField<3, 3, u64> pred3; + BitField<0, 3, u64> pred0; + BitField<43, 1, u64> negate_a; + BitField<44, 1, u64> abs_a; + BitField<47, 2, HalfType> type_a; + BitField<31, 1, u64> negate_b; + BitField<30, 1, u64> abs_b; + BitField<28, 2, HalfType> type_b; + BitField<42, 1, u64> neg_pred; + BitField<39, 3, u64> pred39; + } hsetp2; + + union { BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred; BitField<43, 1, u64> neg_a; @@ -727,10 +831,24 @@ union Instruction { BitField<53, 1, u64> neg_b; BitField<54, 1, u64> abs_a; BitField<55, 1, u64> ftz; - BitField<56, 1, u64> neg_imm; } fset; union { + BitField<49, 1, u64> bf; + BitField<35, 3, PredCondition> cond; + BitField<50, 1, u64> ftz; + BitField<45, 2, PredOperation> op; + BitField<43, 1, u64> negate_a; + BitField<44, 1, u64> abs_a; + BitField<47, 2, HalfType> type_a; + BitField<31, 1, u64> negate_b; + BitField<30, 1, u64> abs_b; + BitField<28, 2, HalfType> type_b; + BitField<42, 1, u64> neg_pred; + BitField<39, 3, u64> pred39; + } hset2; + + union { BitField<39, 3, u64> pred39; BitField<42, 1, u64> neg_pred; BitField<44, 1, u64> bf; @@ -1147,6 +1265,18 @@ public: LEA_RZ, LEA_IMM, LEA_HI, + HADD2_C, + HADD2_R, + HADD2_IMM, + HMUL2_C, + HMUL2_R, + HMUL2_IMM, + HFMA2_CR, + HFMA2_RC, + HFMA2_RR, + HFMA2_IMM_R, + HSETP2_R, + HSET2_R, POPC_C, POPC_R, POPC_IMM, @@ -1220,9 +1350,12 @@ public: ArithmeticImmediate, ArithmeticInteger, ArithmeticIntegerImmediate, + ArithmeticHalf, + ArithmeticHalfImmediate, Bfe, Shift, Ffma, + Hfma2, Flow, Synch, Memory, @@ -1230,6 +1363,8 @@ public: FloatSetPredicate, IntegerSet, IntegerSetPredicate, + HalfSet, + HalfSetPredicate, PredicateSetPredicate, PredicateSetRegister, Conversion, @@ -1393,6 +1528,18 @@ private: INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"), INST("010010111101----", Id::LEA_RZ, Type::ArithmeticInteger, "LEA_RZ"), INST("00011000--------", Id::LEA_HI, Type::ArithmeticInteger, "LEA_HI"), + INST("0111101-1-------", Id::HADD2_C, Type::ArithmeticHalf, "HADD2_C"), + INST("0101110100010---", Id::HADD2_R, Type::ArithmeticHalf, "HADD2_R"), + INST("0111101-0-------", Id::HADD2_IMM, Type::ArithmeticHalfImmediate, "HADD2_IMM"), + INST("0111100-1-------", Id::HMUL2_C, Type::ArithmeticHalf, "HMUL2_C"), + INST("0101110100001---", Id::HMUL2_R, Type::ArithmeticHalf, "HMUL2_R"), + INST("0111100-0-------", Id::HMUL2_IMM, Type::ArithmeticHalfImmediate, "HMUL2_IMM"), + INST("01110---1-------", Id::HFMA2_CR, Type::Hfma2, "HFMA2_CR"), + INST("01100---1-------", Id::HFMA2_RC, Type::Hfma2, "HFMA2_RC"), + INST("0101110100000---", Id::HFMA2_RR, Type::Hfma2, "HFMA2_RR"), + INST("01110---0-------", Id::HFMA2_IMM_R, Type::Hfma2, "HFMA2_R_IMM"), + INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP_R"), + INST("0101110100011---", Id::HSET2_R, Type::HalfSet, "HSET2_R"), INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"), INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"), INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"), diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 9ba7e3533..83c7e5b0b 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -27,8 +27,8 @@ GPU::GPU(VideoCore::RasterizerInterface& rasterizer) { maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager); fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager); maxwell_compute = std::make_unique<Engines::MaxwellCompute>(); - maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager); - kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager); + maxwell_dma = std::make_unique<Engines::MaxwellDMA>(rasterizer, *memory_manager); + kepler_memory = std::make_unique<Engines::KeplerMemory>(rasterizer, *memory_manager); } GPU::~GPU() = default; diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index ca923d17d..022d4ab74 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp @@ -87,6 +87,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) { return gpu_addr; } +GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const { + for (const auto& region : mapped_regions) { + const GPUVAddr region_end{region.gpu_addr + region.size}; + if (region_start >= region.gpu_addr && region_start < region_end) { + return region_end; + } + } + return {}; +} + boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) { GPUVAddr gpu_addr = 0; u64 free_space = 0; diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 86765e72a..caf80093f 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h @@ -26,6 +26,7 @@ public: GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size); GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size); GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size); + GPUVAddr GetRegionEnd(GPUVAddr region_start) const; boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr); std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const; diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h index 083b283b0..0a3b3951e 100644 --- a/src/video_core/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache.h @@ -11,32 +11,77 @@ #include "common/common_types.h" #include "core/core.h" +#include "core/settings.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" +class RasterizerCacheObject { +public: + /// Gets the address of the shader in guest memory, required for cache management + virtual VAddr GetAddr() const = 0; + + /// Gets the size of the shader in guest memory, required for cache management + virtual std::size_t GetSizeInBytes() const = 0; + + /// Wriets any cached resources back to memory + virtual void Flush() = 0; + + /// Sets whether the cached object should be considered registered + void SetIsRegistered(bool registered) { + is_registered = registered; + } + + /// Returns true if the cached object is registered + bool IsRegistered() const { + return is_registered; + } + + /// Returns true if the cached object is dirty + bool IsDirty() const { + return is_dirty; + } + + /// Returns ticks from when this cached object was last modified + u64 GetLastModifiedTicks() const { + return last_modified_ticks; + } + + /// Marks an object as recently modified, used to specify whether it is clean or dirty + template <class T> + void MarkAsModified(bool dirty, T& cache) { + is_dirty = dirty; + last_modified_ticks = cache.GetModifiedTicks(); + } + +private: + bool is_registered{}; ///< Whether the object is currently registered with the cache + bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory) + u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing +}; + template <class T> class RasterizerCache : NonCopyable { + friend class RasterizerCacheObject; + public: + /// Write any cached resources overlapping the specified region back to memory + void FlushRegion(Tegra::GPUVAddr addr, size_t size) { + const auto& objects{GetSortedObjectsFromRegion(addr, size)}; + for (auto& object : objects) { + FlushObject(object); + } + } + /// Mark the specified region as being invalidated void InvalidateRegion(VAddr addr, u64 size) { - if (size == 0) - return; - - const ObjectInterval interval{addr, addr + size}; - for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) { - for (auto& cached_object : pair.second) { - if (!cached_object) - continue; - - remove_objects.emplace(cached_object); + const auto& objects{GetSortedObjectsFromRegion(addr, size)}; + for (auto& object : objects) { + if (!object->IsRegistered()) { + // Skip duplicates + continue; } + Unregister(object); } - - for (auto& remove_object : remove_objects) { - Unregister(remove_object); - } - - remove_objects.clear(); } /// Invalidates everything in the cache @@ -62,6 +107,7 @@ protected: /// Register an object into the cache void Register(const T& object) { + object->SetIsRegistered(true); object_cache.add({GetInterval(object), ObjectSet{object}}); auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer(); rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1); @@ -69,12 +115,57 @@ protected: /// Unregisters an object from the cache void Unregister(const T& object) { + object->SetIsRegistered(false); auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer(); rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1); + + // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit + if (Settings::values.use_accurate_gpu_emulation) { + FlushObject(object); + } + object_cache.subtract({GetInterval(object), ObjectSet{object}}); } + /// Returns a ticks counter used for tracking when cached objects were last modified + u64 GetModifiedTicks() { + return ++modified_ticks; + } + private: + /// Returns a list of cached objects from the specified memory region, ordered by access time + std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) { + if (size == 0) { + return {}; + } + + std::vector<T> objects; + const ObjectInterval interval{addr, addr + size}; + for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) { + for (auto& cached_object : pair.second) { + if (!cached_object) { + continue; + } + objects.push_back(cached_object); + } + } + + std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool { + return a->GetLastModifiedTicks() < b->GetLastModifiedTicks(); + }); + + return objects; + } + + /// Flushes the specified object, updating appropriate cache state as needed + void FlushObject(const T& object) { + if (!object->IsDirty()) { + return; + } + object->Flush(); + object->MarkAsModified(false, *this); + } + using ObjectSet = std::set<T>; using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>; using ObjectInterval = typename ObjectCache::interval_type; @@ -84,6 +175,6 @@ private: object->GetAddr() + object->GetSizeInBytes()); } - ObjectCache object_cache; - ObjectSet remove_objects; + ObjectCache object_cache; ///< Cache of objects + u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing }; diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h index 965976334..be29dc8be 100644 --- a/src/video_core/renderer_opengl/gl_buffer_cache.h +++ b/src/video_core/renderer_opengl/gl_buffer_cache.h @@ -15,15 +15,18 @@ namespace OpenGL { -struct CachedBufferEntry final { - VAddr GetAddr() const { +struct CachedBufferEntry final : public RasterizerCacheObject { + VAddr GetAddr() const override { return addr; } - std::size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const override { return size; } + // We do not have to flush this cache as things in it are never modified by us. + void Flush() override {} + VAddr addr; std::size_t size; GLintptr offset; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8d5f277e2..3daccf82f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -424,6 +424,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep // 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); + + 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); + } + glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D, @@ -434,6 +441,13 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep std::array<GLenum, Maxwell::NumRenderTargets> buffers; for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) { Surface color_surface = res_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); + } + buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index); glFramebufferTexture2D( GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index), @@ -453,6 +467,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep } 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); + if (regs.stencil_enable) { // Attach both depth and stencil glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, @@ -617,7 +635,14 @@ void RasterizerOpenGL::DrawArrays() { void RasterizerOpenGL::FlushAll() {} -void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {} +void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { + MICROPROFILE_SCOPE(OpenGL_CacheManagement); + + if (Settings::values.use_accurate_gpu_emulation) { + // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit + res_cache.FlushRegion(addr, size); + } +} void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { MICROPROFILE_SCOPE(OpenGL_CacheManagement); @@ -627,12 +652,19 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) { } void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) { + FlushRegion(addr, size); InvalidateRegion(addr, size); } bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src, const Tegra::Engines::Fermi2D::Regs::Surface& dst) { MICROPROFILE_SCOPE(OpenGL_Blits); + + if (Settings::values.use_accurate_gpu_emulation) { + // Skip the accelerated copy and perform a slow but more accurate copy + return false; + } + res_cache.FermiCopySurface(src, dst); return true; } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 801d45144..9c8925383 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -34,16 +34,53 @@ struct FormatTuple { bool compressed; }; -static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { - auto& gpu{Core::System::GetInstance().GPU()}; - const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)}; - return cpu_addr ? *cpu_addr : 0; +static bool IsPixelFormatASTC(PixelFormat format) { + switch (format) { + case PixelFormat::ASTC_2D_4X4: + case PixelFormat::ASTC_2D_5X4: + case PixelFormat::ASTC_2D_8X8: + case PixelFormat::ASTC_2D_8X5: + return true; + default: + return false; + } +} + +static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { + switch (format) { + case PixelFormat::ASTC_2D_4X4: + return {4, 4}; + case PixelFormat::ASTC_2D_5X4: + return {5, 4}; + case PixelFormat::ASTC_2D_8X8: + return {8, 8}; + case PixelFormat::ASTC_2D_8X5: + return {8, 5}; + default: + LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format)); + UNREACHABLE(); + } +} + +void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)}; + + addr = cpu_addr ? *cpu_addr : 0; + gpu_addr = 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(); + } } /*static*/ SurfaceParams SurfaceParams::CreateForTexture( const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) { SurfaceParams params{}; - params.addr = TryGetCpuAddr(config.tic.Address()); 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, @@ -87,18 +124,18 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { break; } - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); 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.addr = TryGetCpuAddr(config.Address()); + params.is_tiled = config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; params.block_width = 1 << config.memory_layout.block_width; @@ -112,16 +149,17 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.unaligned_height = config.height; params.target = SurfaceTarget::Texture2D; params.depth = 1; - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = 0; // Render target specific parameters, not used for caching params.rt.index = static_cast<u32>(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; } @@ -130,7 +168,7 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { u32 block_width, u32 block_height, u32 block_depth, Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { SurfaceParams params{}; - params.addr = TryGetCpuAddr(zeta_address); + 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); @@ -143,18 +181,18 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.unaligned_height = zeta_height; params.target = SurfaceTarget::Texture2D; params.depth = 1; - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = 0; params.rt = {}; + params.InitCacheParameters(zeta_address); + return params; } /*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface( const Tegra::Engines::Fermi2D::Regs::Surface& config) { SurfaceParams params{}; - params.addr = TryGetCpuAddr(config.Address()); + 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, @@ -167,11 +205,11 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) { params.unaligned_height = config.height; params.target = SurfaceTarget::Texture2D; params.depth = 1; - params.size_in_bytes_total = params.SizeInBytesTotal(); - params.size_in_bytes_2d = params.SizeInBytes2D(); params.max_mip_level = 0; params.rt = {}; + params.InitCacheParameters(config.Address()); + return params; } @@ -276,34 +314,6 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType return format; } -static bool IsPixelFormatASTC(PixelFormat format) { - switch (format) { - case PixelFormat::ASTC_2D_4X4: - case PixelFormat::ASTC_2D_5X4: - case PixelFormat::ASTC_2D_8X8: - case PixelFormat::ASTC_2D_8X5: - return true; - default: - return false; - } -} - -static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { - switch (format) { - case PixelFormat::ASTC_2D_4X4: - return {4, 4}; - case PixelFormat::ASTC_2D_5X4: - return {5, 4}; - case PixelFormat::ASTC_2D_8X8: - return {8, 8}; - case PixelFormat::ASTC_2D_8X5: - return {8, 5}; - default: - LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format)); - UNREACHABLE(); - } -} - MathUtil::Rectangle<u32> SurfaceParams::GetRect() const { u32 actual_height{unaligned_height}; if (IsPixelFormatASTC(pixel_format)) { @@ -333,23 +343,21 @@ static bool IsFormatBCn(PixelFormat format) { template <bool morton_to_gl, PixelFormat format> void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer, std::size_t gl_buffer_size, VAddr addr) { - constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT; - constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); + constexpr u32 bytes_per_pixel = SurfaceParams::GetBytesPerPixel(format); + + // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual + // pixel values. + const u32 tile_size{IsFormatBCn(format) ? 4U : 1U}; if (morton_to_gl) { - // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual - // pixel values. - const u32 tile_size{IsFormatBCn(format) ? 4U : 1U}; const std::vector<u8> data = Tegra::Texture::UnswizzleTexture( addr, tile_size, bytes_per_pixel, stride, height, depth, block_height, block_depth); const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())}; memcpy(gl_buffer, data.data(), size_to_copy); } else { - // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should - // check the configuration for this and perform more generic un/swizzle - LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!"); - VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel, - Memory::GetPointer(addr), gl_buffer, morton_to_gl); + Tegra::Texture::CopySwizzledData(stride / tile_size, height / tile_size, depth, + bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), + gl_buffer, false, block_height, block_depth); } } @@ -430,17 +438,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, MortonCopy<false, PixelFormat::RGBA16UI>, MortonCopy<false, PixelFormat::R11FG11FB10F>, MortonCopy<false, PixelFormat::RGBA32UI>, - // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4 - // formats are not supported - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, - nullptr, + MortonCopy<false, PixelFormat::DXT1>, + MortonCopy<false, PixelFormat::DXT23>, + MortonCopy<false, PixelFormat::DXT45>, + MortonCopy<false, PixelFormat::DXN1>, + MortonCopy<false, PixelFormat::DXN2UNORM>, + MortonCopy<false, PixelFormat::DXN2SNORM>, + MortonCopy<false, PixelFormat::BC7U>, + MortonCopy<false, PixelFormat::BC6H_UF16>, + MortonCopy<false, PixelFormat::BC6H_SF16>, + // TODO(Subv): Swizzling ASTC formats are not supported nullptr, MortonCopy<false, PixelFormat::G8R8U>, MortonCopy<false, PixelFormat::G8R8S>, @@ -626,22 +633,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type); auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type); - std::size_t buffer_size = - std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total); + 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_DRAW_ARB); if (source_format.compressed) { glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment, - static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr); + static_cast<GLsizei>(src_params.size_in_bytes), nullptr); } else { glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format, - source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total), + source_format.type, static_cast<GLsizei>(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_total < dst_params.size_in_bytes_total) { + 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 @@ -651,12 +657,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during " "reinterpretation but the texture is tiled."); } - std::size_t remaining_size = - dst_params.size_in_bytes_total - src_params.size_in_bytes_total; + std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes; std::vector<u8> data(remaining_size); - Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(), - data.size()); - glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size, + std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes), + data.size()); + + glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size, data.data()); } @@ -702,7 +708,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface, } CachedSurface::CachedSurface(const SurfaceParams& params) - : params(params), gl_target(SurfaceTargetToGL(params.target)) { + : params(params), gl_target(SurfaceTargetToGL(params.target)), + cached_size_in_bytes(params.size_in_bytes) { texture.Create(); const auto& rect{params.GetRect()}; @@ -752,9 +759,21 @@ CachedSurface::CachedSurface(const SurfaceParams& params) VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, SurfaceParams::SurfaceTargetName(params.target)); + + // Clamp size to mapped GPU memory region + // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000 + // R32F render buffer. We do not yet know if this is a game bug or something else, but this + // check is necessary to prevent flushing from overwriting unmapped memory. + + auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()}; + const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr}; + if (cached_size_in_bytes > max_size) { + LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size); + cached_size_in_bytes = max_size; + } } -static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { +static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) { union S8Z24 { BitField<0, 24, u32> z24; BitField<24, 8, u32> s8; @@ -767,22 +786,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) { }; static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size"); - S8Z24 input_pixel{}; - Z24S8 output_pixel{}; - constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)}; + S8Z24 s8z24_pixel{}; + Z24S8 z24s8_pixel{}; + constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::S8Z24)}; for (std::size_t y = 0; y < height; ++y) { for (std::size_t x = 0; x < width; ++x) { const std::size_t offset{bpp * (y * width + x)}; - std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24)); - output_pixel.s8.Assign(input_pixel.s8); - output_pixel.z24.Assign(input_pixel.z24); - std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8)); + if (reverse) { + std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8)); + s8z24_pixel.s8.Assign(z24s8_pixel.s8); + s8z24_pixel.z24.Assign(z24s8_pixel.z24); + std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24)); + } else { + std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24)); + z24s8_pixel.s8.Assign(s8z24_pixel.s8); + z24s8_pixel.z24.Assign(s8z24_pixel.z24); + std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8)); + } } } } static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) { - constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)}; + constexpr auto bpp{SurfaceParams::GetBytesPerPixel(PixelFormat::G8R8U)}; for (std::size_t y = 0; y < height; ++y) { for (std::size_t x = 0; x < width; ++x) { const std::size_t offset{bpp * (y * width + x)}; @@ -814,7 +840,7 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma } case PixelFormat::S8Z24: // Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24. - ConvertS8Z24ToZ24S8(data, width, height); + ConvertS8Z24ToZ24S8(data, width, height, false); break; case PixelFormat::G8R8U: @@ -825,22 +851,36 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma } } +/** + * Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to + * Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or + * with typical desktop GPUs. + */ +static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format, + u32 width, u32 height) { + switch (pixel_format) { + case PixelFormat::G8R8U: + case PixelFormat::G8R8S: + case PixelFormat::ASTC_2D_4X4: + case PixelFormat::ASTC_2D_8X8: { + LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented", + static_cast<u32>(pixel_format)); + UNREACHABLE(); + break; + } + case PixelFormat::S8Z24: + // Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24. + ConvertS8Z24ToZ24S8(data, width, height, true); + break; + } +} + MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); void CachedSurface::LoadGLBuffer() { - ASSERT(params.type != SurfaceType::Fill); - - const u8* const texture_src_data = Memory::GetPointer(params.addr); - - ASSERT(texture_src_data); - - const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format); - const u32 copy_size = params.width * params.height * bytes_per_pixel; - const std::size_t total_size = copy_size * params.depth; - MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); + gl_buffer.resize(params.size_in_bytes_gl); if (params.is_tiled) { - gl_buffer.resize(total_size); u32 depth = params.depth; u32 block_depth = params.block_depth; @@ -853,13 +893,12 @@ void CachedSurface::LoadGLBuffer() { block_depth = 1U; } - const std::size_t size = copy_size * depth; - morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)]( params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(), - size, params.addr); + gl_buffer.size(), params.addr); } else { - const u8* const texture_src_data_end{texture_src_data + total_size}; + const auto texture_src_data{Memory::GetPointer(params.addr)}; + const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl}; gl_buffer.assign(texture_src_data, texture_src_data_end); } @@ -868,7 +907,44 @@ void CachedSurface::LoadGLBuffer() { MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); void CachedSurface::FlushGLBuffer() { - ASSERT_MSG(false, "Unimplemented"); + MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); + + ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented"); + + // OpenGL temporary buffer needs to be big enough to store raw texture size + gl_buffer.resize(GetSizeInBytes()); + + const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); + // Ensure no bad interactions with GL_UNPACK_ALIGNMENT + ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0); + glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width)); + ASSERT(!tuple.compressed); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glGetTextureImage(texture.handle, 0, tuple.format, tuple.type, gl_buffer.size(), + gl_buffer.data()); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer, params.pixel_format, params.width, + params.height); + ASSERT(params.type != SurfaceType::Fill); + const u8* const texture_src_data = Memory::GetPointer(params.addr); + ASSERT(texture_src_data); + if (params.is_tiled) { + u32 depth = params.depth; + u32 block_depth = params.block_depth; + + ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}", + params.block_width, static_cast<u32>(params.target)); + + if (params.target == SurfaceParams::SurfaceTarget::Texture2D) { + // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented. + depth = 1U; + } + gl_to_morton_fns[static_cast<size_t>(params.pixel_format)]( + params.width, params.block_height, params.height, block_depth, depth, gl_buffer.data(), + gl_buffer.size(), GetAddr()); + } else { + std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes()); + } } MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); @@ -878,9 +954,6 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle MICROPROFILE_SCOPE(OpenGL_TextureUL); - ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height * - GetGLBytesPerPixel(params.pixel_format) * params.depth); - const auto& rect{params.GetRect()}; // Load data from memory to the surface @@ -889,7 +962,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle std::size_t buffer_offset = static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width + static_cast<std::size_t>(x0)) * - GetGLBytesPerPixel(params.pixel_format); + SurfaceParams::GetBytesPerPixel(params.pixel_format); const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type); const GLuint target_tex = texture.handle; @@ -905,7 +978,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle cur_state.Apply(); // Ensure no bad interactions with GL_UNPACK_ALIGNMENT - ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0); + ASSERT(params.width * SurfaceParams::GetBytesPerPixel(params.pixel_format) % 4 == 0); glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width)); glActiveTexture(GL_TEXTURE0); @@ -915,7 +988,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle glCompressedTexImage2D( SurfaceTargetToGL(params.target), 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, - static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]); + static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]); break; case SurfaceParams::SurfaceTarget::Texture3D: case SurfaceParams::SurfaceTarget::Texture2DArray: @@ -923,16 +996,16 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle SurfaceTargetToGL(params.target), 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), static_cast<GLsizei>(params.depth), 0, - static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]); + static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]); break; case SurfaceParams::SurfaceTarget::TextureCubemap: for (std::size_t face = 0; face < params.depth; ++face) { glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, - static_cast<GLsizei>(params.size_in_bytes_2d), + static_cast<GLsizei>(params.SizeInBytesCubeFaceGL()), &gl_buffer[buffer_offset]); - buffer_offset += params.size_in_bytes_2d; + buffer_offset += params.SizeInBytesCubeFace(); } break; default: @@ -942,7 +1015,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle glCompressedTexImage2D( GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0, - static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]); + static_cast<GLsizei>(params.size_in_bytes_gl), &gl_buffer[buffer_offset]); } } else { @@ -971,7 +1044,7 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle y0, static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, &gl_buffer[buffer_offset]); - buffer_offset += params.size_in_bytes_2d; + buffer_offset += params.SizeInBytesCubeFace(); } break; default: @@ -1033,10 +1106,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) { surface->LoadGLBuffer(); surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle); -} - -void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) { - surface->FlushGLBuffer(); + surface->MarkAsModified(false, *this); } Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) { @@ -1053,8 +1123,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres } else if (preserve_contents) { // If surface parameters changed and we care about keeping the previous data, recreate // the surface from the old one - Unregister(surface); Surface new_surface{RecreateSurface(surface, params)}; + Unregister(surface); Register(new_surface); return new_surface; } else { @@ -1105,6 +1175,14 @@ void RasterizerCacheOpenGL::FermiCopySurface( FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false)); } +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()}; + FlushRegion(src_params.addr, dst_params.size_in_bytes); + LoadSurface(dst_surface); +} + Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, const SurfaceParams& new_params) { // Verify surface is compatible for blitting @@ -1113,6 +1191,12 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, // 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; + } + // For compatible surfaces, we can just do fast glCopyImageSubData based copy if (old_params.target == new_params.target && old_params.type == new_params.type && old_params.depth == new_params.depth && old_params.depth == 1 && @@ -1124,11 +1208,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, // If the format is the same, just do a framebuffer blit. This is significantly faster than // using PBOs. The is also likely less accurate, as textures will be converted rather than - // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate + // reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate // surface copy, where pixels are reinterpreted as a new format (without conversion). This // code path uses OpenGL PBOs and is quite slow. - const bool is_blit{old_params.pixel_format == new_params.pixel_format || - !Settings::values.use_accurate_framebuffers}; + const bool is_blit{old_params.pixel_format == new_params.pixel_format}; switch (new_params.target) { case SurfaceParams::SurfaceTarget::Texture2D: @@ -1138,6 +1221,9 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, CopySurface(old_surface, new_surface, copy_pbo.handle); } break; + case SurfaceParams::SurfaceTarget::Texture3D: + AccurateCopySurface(old_surface, new_surface); + break; case SurfaceParams::SurfaceTarget::TextureCubemap: { if (old_params.rt.array_mode != 1) { // TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 0b8ae3eb4..0dd0d90a3 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -18,6 +18,7 @@ #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/textures/decoders.h" #include "video_core/textures/texture.h" namespace OpenGL { @@ -131,6 +132,8 @@ struct SurfaceParams { case Tegra::Texture::TextureType::Texture2D: case Tegra::Texture::TextureType::Texture2DNoMipmap: return SurfaceTarget::Texture2D; + case Tegra::Texture::TextureType::Texture3D: + return SurfaceTarget::Texture3D; case Tegra::Texture::TextureType::TextureCubemap: return SurfaceTarget::TextureCubemap; case Tegra::Texture::TextureType::Texture1DArray: @@ -701,21 +704,42 @@ struct SurfaceParams { return SurfaceType::Invalid; } + /// Returns the sizer in bytes of the specified pixel format + static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) { + if (pixel_format == SurfaceParams::PixelFormat::Invalid) { + return 0; + } + return GetFormatBpp(pixel_format) / CHAR_BIT; + } + /// Returns the rectangle corresponding to this surface MathUtil::Rectangle<u32> GetRect() const; - /// Returns the size of this surface as a 2D texture in bytes, adjusted for compression - std::size_t SizeInBytes2D() 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)}; - ASSERT(width % compression_factor == 0); - ASSERT(height % compression_factor == 0); - return (width / compression_factor) * (height / compression_factor) * - GetFormatBpp(pixel_format) / CHAR_BIT; + 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 total size of this surface in bytes, adjusted for compression - std::size_t SizeInBytesTotal() const { - return SizeInBytes2D() * depth; + /// 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; } /// Creates SurfaceParams from a texture configuration @@ -742,7 +766,9 @@ struct SurfaceParams { other.depth); } - VAddr addr; + /// Initializes parameters for caching, should be called after everything has been initialized + void InitCacheParameters(Tegra::GPUVAddr gpu_addr); + bool is_tiled; u32 block_width; u32 block_height; @@ -754,15 +780,20 @@ struct SurfaceParams { u32 height; u32 depth; u32 unaligned_height; - std::size_t size_in_bytes_total; - std::size_t size_in_bytes_2d; SurfaceTarget target; u32 max_mip_level; + // Parameters used for caching + VAddr addr; + Tegra::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; @@ -775,7 +806,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> { static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) { SurfaceReserveKey res; res.state = params; - res.state.rt = {}; // Ignore rt config in caching + res.state.gpu_addr = {}; // Ignore GPU vaddr in caching + res.state.rt = {}; // Ignore rt config in caching return res; } }; @@ -790,16 +822,20 @@ struct hash<SurfaceReserveKey> { namespace OpenGL { -class CachedSurface final { +class CachedSurface final : public RasterizerCacheObject { public: CachedSurface(const SurfaceParams& params); - VAddr GetAddr() const { + VAddr GetAddr() const override { return params.addr; } - std::size_t GetSizeInBytes() const { - return params.size_in_bytes_total; + std::size_t GetSizeInBytes() const override { + return cached_size_in_bytes; + } + + void Flush() override { + FlushGLBuffer(); } const OGLTexture& Texture() const { @@ -810,13 +846,6 @@ public: return gl_target; } - static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) { - if (format == SurfaceParams::PixelFormat::Invalid) - return 0; - - return SurfaceParams::GetFormatBpp(format) / CHAR_BIT; - } - const SurfaceParams& GetSurfaceParams() const { return params; } @@ -833,6 +862,7 @@ private: std::vector<u8> gl_buffer; SurfaceParams params; GLenum gl_target; + std::size_t cached_size_in_bytes; }; class RasterizerCacheOpenGL final : public RasterizerCache<Surface> { @@ -849,9 +879,6 @@ public: /// Get the color surface based on the framebuffer configuration and the specified render target Surface GetColorBufferSurface(std::size_t index, bool preserve_contents); - /// Flushes the surface to Switch memory - void FlushSurface(const Surface& surface); - /// Tries to find a framebuffer using on the provided CPU address Surface TryFindFramebufferSurface(VAddr addr) const; @@ -875,6 +902,9 @@ private: /// Tries to get a reserved surface for the specified parameters Surface TryGetReservedSurface(const SurfaceParams& params); + /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data + void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface); + /// 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. diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h index 7bb287f56..a210f1731 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.h +++ b/src/video_core/renderer_opengl/gl_shader_cache.h @@ -19,20 +19,21 @@ class CachedShader; using Shader = std::shared_ptr<CachedShader>; using Maxwell = Tegra::Engines::Maxwell3D::Regs; -class CachedShader final { +class CachedShader final : public RasterizerCacheObject { public: CachedShader(VAddr addr, Maxwell::ShaderProgram program_type); - /// Gets the address of the shader in guest memory, required for cache management - VAddr GetAddr() const { + VAddr GetAddr() const override { return addr; } - /// Gets the size of the shader in guest memory, required for cache management - std::size_t GetSizeInBytes() const { + std::size_t GetSizeInBytes() const override { return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64); } + // We do not have to flush this cache as things in it are never modified by us. + void Flush() override {} + /// Gets the shader entries for the shader const GLShader::ShaderEntries& GetShaderEntries() const { return entries; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index db52841f3..d36f190b7 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -30,8 +30,6 @@ using Tegra::Shader::SubOp; constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH; constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header); -enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 }; - constexpr u32 MAX_GEOMETRY_BUFFERS = 6; constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested @@ -377,11 +375,55 @@ public: } /** + * Writes code that does a register assignment to a half float value operation. + * @param reg The destination register to use. + * @param elem The element to use for the operation. + * @param value The code representing the value to assign. Type has to be half float. + * @param merge Half float kind of assignment. + * @param dest_num_components Number of components in the destination. + * @param value_num_components Number of components in the value. + * @param is_saturated Optional, when True, saturates the provided value. + * @param dest_elem Optional, the destination element to use for the operation. + */ + void SetRegisterToHalfFloat(const Register& reg, u64 elem, const std::string& value, + Tegra::Shader::HalfMerge merge, u64 dest_num_components, + u64 value_num_components, bool is_saturated = false, + u64 dest_elem = 0) { + ASSERT_MSG(!is_saturated, "Unimplemented"); + + const std::string result = [&]() { + switch (merge) { + case Tegra::Shader::HalfMerge::H0_H1: + return "uintBitsToFloat(packHalf2x16(" + value + "))"; + case Tegra::Shader::HalfMerge::F32: + // Half float instructions take the first component when doing a float cast. + return "float(" + value + ".x)"; + case Tegra::Shader::HalfMerge::Mrg_H0: + // TODO(Rodrigo): I guess Mrg_H0 and Mrg_H1 take their respective component from the + // pack. I couldn't test this on hardware but it shouldn't really matter since most + // of the time when a Mrg_* flag is used both components will be mirrored. That + // being said, it deserves a test. + return "((" + GetRegisterAsInteger(reg, 0, false) + + " & 0xffff0000) | (packHalf2x16(" + value + ") & 0x0000ffff))"; + case Tegra::Shader::HalfMerge::Mrg_H1: + return "((" + GetRegisterAsInteger(reg, 0, false) + + " & 0x0000ffff) | (packHalf2x16(" + value + ") & 0xffff0000))"; + default: + UNREACHABLE(); + return std::string("0"); + } + }(); + + SetRegister(reg, elem, result, dest_num_components, value_num_components, dest_elem); + } + + /** * Writes code that does a register assignment to input attribute operation. Input attributes * are stored as floats, so this may require conversion. * @param reg The destination register to use. * @param elem The element to use for the operation. * @param attribute The input attribute to use as the source value. + * @param input_mode The input mode. * @param vertex The register that decides which vertex to read from (used in GS). */ void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute, @@ -549,13 +591,6 @@ private: /// Generates declarations for input attributes. void GenerateInputAttrs() { - if (stage != Maxwell3D::Regs::ShaderStage::Vertex) { - const std::string attr = - stage == Maxwell3D::Regs::ShaderStage::Geometry ? "gs_position[]" : "position"; - declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + - ") in vec4 " + attr + ';'); - } - for (const auto element : declr_input_attribute) { // TODO(bunnei): Use proper number of elements for these u32 idx = @@ -578,10 +613,6 @@ private: /// Generates declarations for output attributes. void GenerateOutputAttrs() { - if (stage != Maxwell3D::Regs::ShaderStage::Fragment) { - declarations.AddLine("layout (location = " + std::to_string(POSITION_VARYING_LOCATION) + - ") out vec4 position;"); - } for (const auto& index : declr_output_attribute) { // TODO(bunnei): Use proper number of elements for these const u32 idx = static_cast<u32>(index) - @@ -878,6 +909,19 @@ private: return fmt::format("uintBitsToFloat({})", instr.alu.GetImm20_32()); } + /// Generates code representing a vec2 pair unpacked from a half float immediate + static std::string UnpackHalfImmediate(const Instruction& instr, bool negate) { + const std::string immediate = GetHalfFloat(std::to_string(instr.half_imm.PackImmediates())); + if (!negate) { + return immediate; + } + const std::string negate_first = instr.half_imm.first_negate != 0 ? "-" : ""; + const std::string negate_second = instr.half_imm.second_negate != 0 ? "-" : ""; + const std::string negate_vec = "vec2(" + negate_first + "1, " + negate_second + "1)"; + + return '(' + immediate + " * " + negate_vec + ')'; + } + /// Generates code representing a texture sampler. std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array, bool is_shadow) { @@ -909,7 +953,7 @@ private: // Can't assign to the constant predicate. ASSERT(pred != static_cast<u64>(Pred::UnusedIndex)); - const std::string variable = 'p' + std::to_string(pred) + '_' + suffix; + std::string variable = 'p' + std::to_string(pred) + '_' + suffix; shader.AddLine(variable + " = " + value + ';'); declr_predicates.insert(std::move(variable)); } @@ -1014,6 +1058,41 @@ private: } /* + * Transforms the input string GLSL operand into an unpacked half float pair. + * @note This function returns a float type pair instead of a half float pair. This is because + * real half floats are not standardized in GLSL but unpackHalf2x16 (which returns a vec2) is. + * @param operand Input operand. It has to be an unsigned integer. + * @param type How to unpack the unsigned integer to a half float pair. + * @param abs Get the absolute value of unpacked half floats. + * @param neg Get the negative value of unpacked half floats. + * @returns String corresponding to a half float pair. + */ + static std::string GetHalfFloat(const std::string& operand, + Tegra::Shader::HalfType type = Tegra::Shader::HalfType::H0_H1, + bool abs = false, bool neg = false) { + // "vec2" calls emitted in this function are intended to alias components. + const std::string value = [&]() { + switch (type) { + case Tegra::Shader::HalfType::H0_H1: + return "unpackHalf2x16(" + operand + ')'; + case Tegra::Shader::HalfType::F32: + return "vec2(uintBitsToFloat(" + operand + "))"; + case Tegra::Shader::HalfType::H0_H0: + case Tegra::Shader::HalfType::H1_H1: { + const bool high = type == Tegra::Shader::HalfType::H1_H1; + const char unpack_index = "xy"[high ? 1 : 0]; + return "vec2(unpackHalf2x16(" + operand + ")." + unpack_index + ')'; + } + default: + UNREACHABLE(); + return std::string("vec2(0)"); + } + }(); + + return GetOperandAbsNeg(value, abs, neg); + } + + /* * Returns whether the instruction at the specified offset is a 'sched' instruction. * Sched instructions always appear before a sequence of 3 instructions. */ @@ -1143,6 +1222,7 @@ private: case Tegra::Shader::TextureType::Texture2D: { return 2; } + case Tegra::Shader::TextureType::Texture3D: case Tegra::Shader::TextureType::TextureCube: { return 3; } @@ -1381,9 +1461,10 @@ private: break; } case OpCode::Id::FMUL32_IMM: { - regs.SetRegisterToFloat( - instr.gpr0, 0, - regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + regs.SetRegisterToFloat(instr.gpr0, 0, + regs.GetRegisterAsFloat(instr.gpr8) + " * " + + GetImmediate32(instr), + 1, 1, instr.fmul32.saturate); break; } case OpCode::Id::FADD32I: { @@ -1748,6 +1829,86 @@ private: break; } + case OpCode::Type::ArithmeticHalf: { + if (opcode->GetId() == OpCode::Id::HADD2_C || opcode->GetId() == OpCode::Id::HADD2_R) { + ASSERT_MSG(instr.alu_half.ftz == 0, "Unimplemented"); + } + const bool negate_a = + opcode->GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0; + const bool negate_b = + opcode->GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0; + + const std::string op_a = + GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.alu_half.type_a, + instr.alu_half.abs_a != 0, negate_a); + + std::string op_b; + switch (opcode->GetId()) { + case OpCode::Id::HADD2_C: + case OpCode::Id::HMUL2_C: + op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::UnsignedInteger); + break; + case OpCode::Id::HADD2_R: + case OpCode::Id::HMUL2_R: + op_b = regs.GetRegisterAsInteger(instr.gpr20, 0, false); + break; + default: + UNREACHABLE(); + op_b = "0"; + break; + } + op_b = GetHalfFloat(op_b, instr.alu_half.type_b, instr.alu_half.abs_b != 0, negate_b); + + const std::string result = [&]() { + switch (opcode->GetId()) { + case OpCode::Id::HADD2_C: + case OpCode::Id::HADD2_R: + return '(' + op_a + " + " + op_b + ')'; + case OpCode::Id::HMUL2_C: + case OpCode::Id::HMUL2_R: + return '(' + op_a + " * " + op_b + ')'; + default: + LOG_CRITICAL(HW_GPU, "Unhandled half float instruction: {}", opcode->GetName()); + UNREACHABLE(); + return std::string("0"); + } + }(); + + regs.SetRegisterToHalfFloat(instr.gpr0, 0, result, instr.alu_half.merge, 1, 1, + instr.alu_half.saturate != 0); + break; + } + case OpCode::Type::ArithmeticHalfImmediate: { + if (opcode->GetId() == OpCode::Id::HADD2_IMM) { + ASSERT_MSG(instr.alu_half_imm.ftz == 0, "Unimplemented"); + } else { + ASSERT_MSG(instr.alu_half_imm.precision == Tegra::Shader::HalfPrecision::None, + "Unimplemented"); + } + + const std::string op_a = GetHalfFloat( + regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.alu_half_imm.type_a, + instr.alu_half_imm.abs_a != 0, instr.alu_half_imm.negate_a != 0); + + const std::string op_b = UnpackHalfImmediate(instr, true); + + const std::string result = [&]() { + switch (opcode->GetId()) { + case OpCode::Id::HADD2_IMM: + return op_a + " + " + op_b; + case OpCode::Id::HMUL2_IMM: + return op_a + " * " + op_b; + default: + UNREACHABLE(); + return std::string("0"); + } + }(); + + regs.SetRegisterToHalfFloat(instr.gpr0, 0, result, instr.alu_half_imm.merge, 1, 1, + instr.alu_half_imm.saturate != 0); + break; + } case OpCode::Type::Ffma: { const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8); std::string op_b = instr.ffma.negate_b ? "-" : ""; @@ -1792,6 +1953,59 @@ private: instr.alu.saturate_d); break; } + case OpCode::Type::Hfma2: { + if (opcode->GetId() == OpCode::Id::HFMA2_RR) { + ASSERT_MSG(instr.hfma2.rr.precision == Tegra::Shader::HalfPrecision::None, + "Unimplemented"); + } else { + ASSERT_MSG(instr.hfma2.precision == Tegra::Shader::HalfPrecision::None, + "Unimplemented"); + } + const bool saturate = opcode->GetId() == OpCode::Id::HFMA2_RR + ? instr.hfma2.rr.saturate != 0 + : instr.hfma2.saturate != 0; + + const std::string op_a = + GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hfma2.type_a); + std::string op_b, op_c; + + switch (opcode->GetId()) { + case OpCode::Id::HFMA2_CR: + op_b = GetHalfFloat(regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::UnsignedInteger), + instr.hfma2.type_b, false, instr.hfma2.negate_b); + op_c = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false), + instr.hfma2.type_reg39, false, instr.hfma2.negate_c); + break; + case OpCode::Id::HFMA2_RC: + op_b = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false), + instr.hfma2.type_reg39, false, instr.hfma2.negate_b); + op_c = GetHalfFloat(regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::UnsignedInteger), + instr.hfma2.type_b, false, instr.hfma2.negate_c); + break; + case OpCode::Id::HFMA2_RR: + op_b = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.hfma2.type_b, false, instr.hfma2.negate_b); + op_c = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false), + instr.hfma2.rr.type_c, false, instr.hfma2.rr.negate_c); + break; + case OpCode::Id::HFMA2_IMM_R: + op_b = UnpackHalfImmediate(instr, true); + op_c = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false), + instr.hfma2.type_reg39, false, instr.hfma2.negate_c); + break; + default: + UNREACHABLE(); + op_c = op_b = "vec2(0)"; + break; + } + + const std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; + + regs.SetRegisterToHalfFloat(instr.gpr0, 0, result, instr.hfma2.merge, 1, 1, saturate); + break; + } case OpCode::Type::Conversion: { switch (opcode->GetId()) { case OpCode::Id::I2I_R: { @@ -2037,9 +2251,9 @@ private: break; } case OpCode::Id::TEX: { - ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented"); Tegra::Shader::TextureType texture_type{instr.tex.texture_type}; std::string coord; + const bool is_array = instr.tex.array != 0; ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP), "NODEP is not implemented"); @@ -2054,21 +2268,59 @@ private: switch (num_coordinates) { case 1: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - coord = "float coords = " + x + ';'; + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + index + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + coord = "float coords = " + x + ';'; + } break; } case 2: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + coord = "vec2 coords = vec2(" + x + ", " + y + ");"; + } break; } case 3: { - const std::string x = regs.GetRegisterAsFloat(instr.gpr8); - const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); - const std::string z = regs.GetRegisterAsFloat(instr.gpr20); - coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + if (depth_compare) { + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr20); + const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr20); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + } + } else { + if (is_array) { + const std::string index = regs.GetRegisterAsInteger(instr.gpr8); + const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 3); + coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index + + ");"; + } else { + const std::string x = regs.GetRegisterAsFloat(instr.gpr8); + const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1); + const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2); + coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");"; + } + } break; } default: @@ -2087,7 +2339,7 @@ private: std::string op_c; const std::string sampler = - GetSampler(instr.sampler, texture_type, false, depth_compare); + GetSampler(instr.sampler, texture_type, is_array, depth_compare); // Add an extra scope and declare the texture coords inside to prevent // overwriting them in case they are used as outputs of the texs instruction. @@ -2107,10 +2359,13 @@ private: } case Tegra::Shader::TextureProcessMode::LB: case Tegra::Shader::TextureProcessMode::LBA: { - if (num_coordinates <= 2) { - op_c = regs.GetRegisterAsFloat(instr.gpr20); + if (depth_compare) { + if (is_array) + op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 2); + else + op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); } else { - op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1); + op_c = regs.GetRegisterAsFloat(instr.gpr20); } // TODO: Figure if A suffix changes the equation at all. texture = "texture(" + sampler + ", coords, " + op_c + ')'; @@ -2253,6 +2508,8 @@ private: ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ), "MZ is not implemented"); + u32 op_c_offset = 0; + switch (texture_type) { case Tegra::Shader::TextureType::Texture1D: { const std::string x = regs.GetRegisterAsInteger(instr.gpr8); @@ -2267,6 +2524,7 @@ private: const std::string x = regs.GetRegisterAsInteger(instr.gpr8); const std::string y = regs.GetRegisterAsInteger(instr.gpr20); coord = "ivec2 coords = ivec2(" + x + ", " + y + ");"; + op_c_offset = 1; } break; } @@ -2278,13 +2536,14 @@ private: const std::string sampler = GetSampler(instr.sampler, texture_type, is_array, false); std::string texture = "texelFetch(" + sampler + ", coords, 0)"; - const std::string op_c = regs.GetRegisterAsInteger(instr.gpr20.Value() + 1); switch (instr.tlds.GetTextureProcessMode()) { case Tegra::Shader::TextureProcessMode::LZ: { texture = "texelFetch(" + sampler + ", coords, 0)"; break; } case Tegra::Shader::TextureProcessMode::LL: { + const std::string op_c = + regs.GetRegisterAsInteger(instr.gpr20.Value() + op_c_offset); texture = "texelFetch(" + sampler + ", coords, " + op_c + ')'; break; } @@ -2480,20 +2739,13 @@ private: break; } case OpCode::Type::FloatSetPredicate: { - std::string op_a = instr.fsetp.neg_a ? "-" : ""; - op_a += regs.GetRegisterAsFloat(instr.gpr8); - - if (instr.fsetp.abs_a) { - op_a = "abs(" + op_a + ')'; - } + const std::string op_a = + GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), instr.fsetp.abs_a != 0, + instr.fsetp.neg_a != 0); - std::string op_b{}; + std::string op_b; if (instr.is_b_imm) { - if (instr.fsetp.neg_b) { - // Only the immediate version of fsetp has a neg_b bit. - op_b += '-'; - } op_b += '(' + GetImmediate19(instr) + ')'; } else { if (instr.is_b_gpr) { @@ -2566,6 +2818,51 @@ private: } break; } + case OpCode::Type::HalfSetPredicate: { + ASSERT_MSG(instr.hsetp2.ftz == 0, "Unimplemented"); + + const std::string op_a = + GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hsetp2.type_a, + instr.hsetp2.abs_a, instr.hsetp2.negate_a); + + const std::string op_b = [&]() { + switch (opcode->GetId()) { + case OpCode::Id::HSETP2_R: + return GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.hsetp2.type_b, instr.hsetp2.abs_a, + instr.hsetp2.negate_b); + default: + UNREACHABLE(); + return std::string("vec2(0)"); + } + }(); + + // We can't use the constant predicate as destination. + ASSERT(instr.hsetp2.pred3 != static_cast<u64>(Pred::UnusedIndex)); + + const std::string second_pred = + GetPredicateCondition(instr.hsetp2.pred39, instr.hsetp2.neg_pred != 0); + + const std::string combiner = GetPredicateCombiner(instr.hsetp2.op); + + const std::string component_combiner = instr.hsetp2.h_and ? "&&" : "||"; + const std::string predicate = + '(' + GetPredicateComparison(instr.hsetp2.cond, op_a + ".x", op_b + ".x") + ' ' + + component_combiner + ' ' + + GetPredicateComparison(instr.hsetp2.cond, op_a + ".y", op_b + ".y") + ')'; + + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.hsetp2.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + + if (instr.hsetp2.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.hsetp2.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } case OpCode::Type::PredicateSetRegister: { const std::string op_a = GetPredicateCondition(instr.pset.pred12, instr.pset.neg_pred12 != 0); @@ -2644,33 +2941,24 @@ private: break; } case OpCode::Type::FloatSet: { - std::string op_a = instr.fset.neg_a ? "-" : ""; - op_a += regs.GetRegisterAsFloat(instr.gpr8); + const std::string op_a = GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), + instr.fset.abs_a != 0, instr.fset.neg_a != 0); - if (instr.fset.abs_a) { - op_a = "abs(" + op_a + ')'; - } - - std::string op_b = instr.fset.neg_b ? "-" : ""; + std::string op_b; if (instr.is_b_imm) { const std::string imm = GetImmediate19(instr); - if (instr.fset.neg_imm) - op_b += "(-" + imm + ')'; - else - op_b += imm; + op_b = imm; } else { if (instr.is_b_gpr) { - op_b += regs.GetRegisterAsFloat(instr.gpr20); + op_b = regs.GetRegisterAsFloat(instr.gpr20); } else { - op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, - GLSLRegister::Type::Float); + op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); } } - if (instr.fset.abs_b) { - op_b = "abs(" + op_b + ')'; - } + op_b = GetOperandAbsNeg(op_b, instr.fset.abs_b != 0, instr.fset.neg_b != 0); // The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the // condition is true, and to 0 otherwise. @@ -2726,6 +3014,50 @@ private: } break; } + case OpCode::Type::HalfSet: { + ASSERT_MSG(instr.hset2.ftz == 0, "Unimplemented"); + + const std::string op_a = + GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hset2.type_a, + instr.hset2.abs_a != 0, instr.hset2.negate_a != 0); + + const std::string op_b = [&]() { + switch (opcode->GetId()) { + case OpCode::Id::HSET2_R: + return GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.hset2.type_b, instr.hset2.abs_b != 0, + instr.hset2.negate_b != 0); + default: + UNREACHABLE(); + return std::string("vec2(0)"); + } + }(); + + const std::string second_pred = + GetPredicateCondition(instr.hset2.pred39, instr.hset2.neg_pred != 0); + + const std::string combiner = GetPredicateCombiner(instr.hset2.op); + + // HSET2 operates on each half float in the pack. + std::string result; + for (int i = 0; i < 2; ++i) { + const std::string float_value = i == 0 ? "0x00003c00" : "0x3c000000"; + const std::string integer_value = i == 0 ? "0x0000ffff" : "0xffff0000"; + const std::string value = instr.hset2.bf == 1 ? float_value : integer_value; + + const std::string comp = std::string(".") + "xy"[i]; + const std::string predicate = + "((" + GetPredicateComparison(instr.hset2.cond, op_a + comp, op_b + comp) + + ") " + combiner + " (" + second_pred + "))"; + + result += '(' + predicate + " ? " + value + " : 0)"; + if (i == 0) { + result += " | "; + } + } + regs.SetRegisterToInteger(instr.gpr0, false, 0, '(' + result + ')', 1, 1); + break; + } case OpCode::Type::Xmad: { ASSERT_MSG(!instr.xmad.sign_a, "Unimplemented"); ASSERT_MSG(!instr.xmad.sign_b, "Unimplemented"); @@ -2978,8 +3310,8 @@ private: Tegra::Shader::VmadType type, u64 byte_height) { const std::string value = [&]() { if (!is_chunk) { - const auto offset = static_cast<u32>(byte_height * 8); - return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)"; + const auto shift = static_cast<u32>(byte_height * 8); + return "((" + op + " >> " + std::to_string(shift) + ") & 0xff)"; } const std::string zero = "0"; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 1e5eb32df..ecbc9d8ed 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -23,6 +23,8 @@ out gl_PerVertex { vec4 gl_Position; }; +layout (location = 0) out vec4 position; + layout(std140) uniform vs_config { vec4 viewport_flip; uvec4 instance_id; @@ -96,6 +98,9 @@ out gl_PerVertex { vec4 gl_Position; }; +layout (location = 0) in vec4 gs_position[]; +layout (location = 0) out vec4 position; + layout (std140) uniform gs_config { vec4 viewport_flip; uvec4 instance_id; @@ -131,6 +136,8 @@ layout(location = 5) out vec4 FragColor5; layout(location = 6) out vec4 FragColor6; layout(location = 7) out vec4 FragColor7; +layout (location = 0) in vec4 position; + layout (std140) uniform fs_config { vec4 viewport_flip; uvec4 instance_id; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 79596087a..520b9d4e3 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -16,6 +16,8 @@ namespace OpenGL::GLShader { constexpr std::size_t MAX_PROGRAM_CODE_LENGTH{0x1000}; using ProgramCode = std::vector<u64>; +enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 }; + class ConstBufferEntry { using Maxwell = Tegra::Engines::Maxwell3D::Regs; diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 3c3bcaae4..0f6dcab2b 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -82,8 +82,20 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) { return {}; } - case Maxwell::VertexAttribute::Type::Float: - return GL_FLOAT; + case Maxwell::VertexAttribute::Type::Float: { + switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_16: + case Maxwell::VertexAttribute::Size::Size_16_16: + case Maxwell::VertexAttribute::Size::Size_16_16_16: + case Maxwell::VertexAttribute::Size::Size_16_16_16_16: + return GL_HALF_FLOAT; + case Maxwell::VertexAttribute::Size::Size_32: + case Maxwell::VertexAttribute::Size::Size_32_32: + case Maxwell::VertexAttribute::Size::Size_32_32_32: + case Maxwell::VertexAttribute::Size::Size_32_32_32_32: + return GL_FLOAT; + } + } } LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString()); diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 18ab723f7..f1b40e7f5 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -237,6 +237,46 @@ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pix return unswizzled_data; } +void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height) { + const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + 63) / 64}; + for (u32 line = 0; line < subrect_height; ++line) { + const u32 gob_address_y = + (line / (8 * block_height)) * 512 * block_height * image_width_in_gobs + + (line % (8 * block_height) / 8) * 512; + const auto& table = legacy_swizzle_table[line % 8]; + for (u32 x = 0; x < subrect_width; ++x) { + const u32 gob_address = gob_address_y + (x * bytes_per_pixel / 64) * 512 * block_height; + const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % 64]; + const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel; + const VAddr dest_addr = swizzled_data + swizzled_offset; + + Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel); + } + } +} + +void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height, u32 offset_x, u32 offset_y) { + for (u32 line = 0; line < subrect_height; ++line) { + const u32 y2 = line + offset_y; + const u32 gob_address_y = + (y2 / (8 * block_height)) * 512 * block_height + (y2 % (8 * block_height) / 8) * 512; + const auto& table = legacy_swizzle_table[y2 % 8]; + for (u32 x = 0; x < subrect_width; ++x) { + const u32 x2 = (x + offset_x) * bytes_per_pixel; + const u32 gob_address = gob_address_y + (x2 / 64) * 512 * block_height; + const u32 swizzled_offset = gob_address + table[x2 % 64]; + const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel; + const VAddr source_addr = swizzled_data + swizzled_offset; + + Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel); + } + } +} + std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width, u32 height) { std::vector<u8> rgba_data; diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h index aaf316947..4726f54a5 100644 --- a/src/video_core/textures/decoders.h +++ b/src/video_core/textures/decoders.h @@ -35,4 +35,13 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& 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); +/// Copies an untiled subrectangle into a tiled surface. +void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height); +/// Copies a tiled subrectangle into a linear surface. +void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width, + u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data, + u32 block_height, u32 offset_x, u32 offset_y); + } // namespace Tegra::Texture |