diff options
Diffstat (limited to 'src/video_core/renderer_opengl')
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 41 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.h | 13 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 135 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.h | 40 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 649 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.cpp | 39 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_gen.h | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_manager.cpp | 11 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_shader_manager.h | 8 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_state.cpp | 8 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_state.h | 8 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/maxwell_to_gl.h | 16 |
12 files changed, 726 insertions, 244 deletions
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 468253033..b472f421f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -570,10 +570,12 @@ void RasterizerOpenGL::DrawArrays() { SyncBlendState(); SyncLogicOpState(); SyncCullMode(); - SyncAlphaTest(); + SyncDepthRange(); SyncScissorTest(); + // Alpha Testing is synced on shaders. SyncTransformFeedback(); SyncPointState(); + CheckAlphaTests(); // TODO(bunnei): Sync framebuffer_scale uniform here // TODO(bunnei): Sync scissorbox uniform(s) here @@ -659,6 +661,12 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 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; } @@ -916,12 +924,11 @@ void RasterizerOpenGL::SyncCullMode() { } } -void RasterizerOpenGL::SyncDepthScale() { - UNREACHABLE(); -} +void RasterizerOpenGL::SyncDepthRange() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; -void RasterizerOpenGL::SyncDepthOffset() { - UNREACHABLE(); + state.depth.depth_range_near = regs.viewport->depth_range_near; + state.depth.depth_range_far = regs.viewport->depth_range_far; } void RasterizerOpenGL::SyncDepthTestState() { @@ -1001,17 +1008,6 @@ void RasterizerOpenGL::SyncLogicOpState() { state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); } -void RasterizerOpenGL::SyncAlphaTest() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - - // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be - // implemented with a test+discard in fragment shaders. - if (regs.alpha_test_enabled != 0) { - LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented"); - UNREACHABLE(); - } -} - void RasterizerOpenGL::SyncScissorTest() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; @@ -1046,4 +1042,15 @@ void RasterizerOpenGL::SyncPointState() { state.point.size = regs.point_size == 0 ? 1 : regs.point_size; } +void RasterizerOpenGL::CheckAlphaTests() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + + if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) { + LOG_CRITICAL( + Render_OpenGL, + "Alpha Testing is enabled with Multiple Render Targets, this behavior is undefined."); + UNREACHABLE(); + } +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index b1f7ccc7e..731a336d5 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -144,11 +144,8 @@ private: /// Syncs the cull mode to match the guest state void SyncCullMode(); - /// Syncs the depth scale to match the guest state - void SyncDepthScale(); - - /// Syncs the depth offset to match the guest state - void SyncDepthOffset(); + /// Syncs the depth range to match the guest state + void SyncDepthRange(); /// Syncs the depth test state to match the guest state void SyncDepthTestState(); @@ -162,9 +159,6 @@ private: /// Syncs the LogicOp state to match the guest state void SyncLogicOpState(); - /// Syncs the alpha test state to match the guest state - void SyncAlphaTest(); - /// Syncs the scissor test state to match the guest state void SyncScissorTest(); @@ -174,6 +168,9 @@ private: /// Syncs the point state to match the guest state void SyncPointState(); + /// Check asserts for alpha testing. + void CheckAlphaTests(); + bool has_ARB_direct_state_access = false; bool has_ARB_multi_bind = false; bool has_ARB_separate_shader_objects = false; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index 9c8925383..591ec7998 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -78,6 +78,29 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { } } +std::size_t SurfaceParams::InnerMemorySize(bool layer_only) const { + const u32 compression_factor{GetCompressionFactor(pixel_format)}; + const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)}; + u32 m_depth = (layer_only ? 1U : depth); + u32 m_width = std::max(1U, width / compression_factor); + u32 m_height = std::max(1U, height / compression_factor); + std::size_t size = Tegra::Texture::CalculateSize(is_tiled, bytes_per_pixel, m_width, m_height, + m_depth, block_height, block_depth); + u32 m_block_height = block_height; + u32 m_block_depth = block_depth; + std::size_t block_size_bytes = 512 * block_height * block_depth; // 512 is GOB size + for (u32 i = 1; i < max_mip_level; i++) { + m_width = std::max(1U, m_width / 2); + m_height = std::max(1U, m_height / 2); + m_depth = std::max(1U, m_depth / 2); + m_block_height = std::max(1U, m_block_height / 2); + m_block_depth = std::max(1U, m_block_depth / 2); + size += Tegra::Texture::CalculateSize(is_tiled, bytes_per_pixel, m_width, m_height, m_depth, + m_block_height, m_block_depth); + } + return is_tiled ? Common::AlignUp(size, block_size_bytes) : size; +} + /*static*/ SurfaceParams SurfaceParams::CreateForTexture( const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) { SurfaceParams params{}; @@ -124,6 +147,7 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { break; } + params.is_layered = SurfaceTargetIsLayered(params.target); params.max_mip_level = config.tic.max_mip_level + 1; params.rt = {}; @@ -150,6 +174,7 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { params.target = SurfaceTarget::Texture2D; params.depth = 1; params.max_mip_level = 0; + params.is_layered = false; // Render target specific parameters, not used for caching params.rt.index = static_cast<u32>(index); @@ -182,6 +207,7 @@ void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) { params.target = SurfaceTarget::Texture2D; params.depth = 1; params.max_mip_level = 0; + params.is_layered = false; params.rt = {}; params.InitCacheParameters(zeta_address); @@ -361,10 +387,11 @@ void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 d } } -static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr), - SurfaceParams::MaxPixelFormat> - morton_to_gl_fns = { - // clang-format off +using GLConversionArray = std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr), + SurfaceParams::MaxPixelFormat>; + +static constexpr GLConversionArray morton_to_gl_fns = { + // clang-format off MortonCopy<true, PixelFormat::ABGR8U>, MortonCopy<true, PixelFormat::ABGR8S>, MortonCopy<true, PixelFormat::ABGR8UI>, @@ -418,13 +445,11 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, MortonCopy<true, PixelFormat::Z24S8>, MortonCopy<true, PixelFormat::S8Z24>, MortonCopy<true, PixelFormat::Z32FS8>, - // clang-format on + // clang-format on }; -static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr), - SurfaceParams::MaxPixelFormat> - gl_to_morton_fns = { - // clang-format off +static constexpr GLConversionArray gl_to_morton_fns = { + // clang-format off MortonCopy<false, PixelFormat::ABGR8U>, MortonCopy<false, PixelFormat::ABGR8S>, MortonCopy<false, PixelFormat::ABGR8UI>, @@ -479,9 +504,35 @@ static constexpr std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, MortonCopy<false, PixelFormat::Z24S8>, MortonCopy<false, PixelFormat::S8Z24>, MortonCopy<false, PixelFormat::Z32FS8>, - // clang-format on + // clang-format on }; +void SwizzleFunc(const GLConversionArray& functions, const SurfaceParams& params, + std::vector<u8>& gl_buffer) { + u32 depth = params.depth; + if (params.target == SurfaceParams::SurfaceTarget::Texture2D) { + // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented. + depth = 1U; + } + if (params.is_layered) { + u64 offset = 0; + u64 offset_gl = 0; + u64 layer_size = params.LayerMemorySize(); + u64 gl_size = params.LayerSizeGL(); + for (u32 i = 0; i < depth; i++) { + functions[static_cast<std::size_t>(params.pixel_format)]( + params.width, params.block_height, params.height, params.block_depth, 1, + gl_buffer.data() + offset_gl, gl_size, params.addr + offset); + offset += layer_size; + offset_gl += gl_size; + } + } else { + functions[static_cast<std::size_t>(params.pixel_format)]( + params.width, params.block_height, params.height, params.block_depth, depth, + gl_buffer.data(), gl_buffer.size(), params.addr); + } +} + static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface, GLuint read_fb_handle, GLuint draw_fb_handle, GLenum src_attachment = 0, GLenum dst_attachment = 0, std::size_t cubemap_face = 0) { @@ -881,21 +932,10 @@ void CachedSurface::LoadGLBuffer() { gl_buffer.resize(params.size_in_bytes_gl); 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; - block_depth = 1U; - } - - 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(), - gl_buffer.size(), params.addr); + SwizzleFunc(morton_to_gl_fns, params, gl_buffer); } else { const auto texture_src_data{Memory::GetPointer(params.addr)}; const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl}; @@ -929,19 +969,10 @@ void CachedSurface::FlushGLBuffer() { 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()); + SwizzleFunc(gl_to_morton_fns, params, gl_buffer); } else { std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer.data(), GetSizeInBytes()); } @@ -1179,7 +1210,7 @@ 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); + FlushRegion(src_params.addr, dst_params.MemorySize()); LoadSurface(dst_surface); } @@ -1221,44 +1252,10 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, CopySurface(old_surface, new_surface, copy_pbo.handle); } break; + case SurfaceParams::SurfaceTarget::TextureCubemap: 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 - // yet (array rendering used as a cubemap texture). - LOG_CRITICAL(HW_GPU, "Unhandled rendertarget array_mode {}", old_params.rt.array_mode); - UNREACHABLE(); - return new_surface; - } - - // This seems to be used for render-to-cubemap texture - ASSERT_MSG(old_params.target == SurfaceParams::SurfaceTarget::Texture2D, "Unexpected"); - ASSERT_MSG(old_params.pixel_format == new_params.pixel_format, "Unexpected"); - ASSERT_MSG(old_params.rt.base_layer == 0, "Unimplemented"); - - // TODO(bunnei): Verify the below - this stride seems to be in 32-bit words, not pixels. - // Tested with Splatoon 2, Super Mario Odyssey, and Breath of the Wild. - const std::size_t byte_stride{old_params.rt.layer_stride * sizeof(u32)}; - - for (std::size_t index = 0; index < new_params.depth; ++index) { - Surface face_surface{TryGetReservedSurface(old_params)}; - ASSERT_MSG(face_surface, "Unexpected"); - - if (is_blit) { - BlitSurface(face_surface, new_surface, read_framebuffer.handle, - draw_framebuffer.handle, face_surface->GetSurfaceParams().rt.index, - new_params.rt.index, index); - } else { - CopySurface(face_surface, new_surface, copy_pbo.handle, - face_surface->GetSurfaceParams().rt.index, new_params.rt.index, index); - } - - old_params.addr += byte_stride; - } - break; - } default: LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}", static_cast<u32>(new_params.target)); @@ -1266,7 +1263,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, } return new_surface; -} +} // namespace OpenGL Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const { return TryGet(addr); diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index 0dd0d90a3..50a7ab47d 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -168,6 +168,23 @@ struct SurfaceParams { } } + static bool SurfaceTargetIsLayered(SurfaceTarget target) { + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: + return false; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + return true; + default: + LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target)); + UNREACHABLE(); + return false; + } + } + /** * Gets the compression factor for the specified PixelFormat. This applies to just the * "compressed width" and "compressed height", not the overall compression factor of a @@ -742,6 +759,25 @@ struct SurfaceParams { 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(is_layered); + if (is_layered) + return size * depth; + return size; + } + + /// 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(true); + } + + /// Returns the size of a layer of this surface in OpenGL. + std::size_t LayerSizeGL() const { + return SizeInBytesRaw(true) / depth; + } + /// Creates SurfaceParams from a texture configuration static SurfaceParams CreateForTexture(const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry); @@ -782,6 +818,7 @@ struct SurfaceParams { u32 unaligned_height; SurfaceTarget target; u32 max_mip_level; + bool is_layered; // Parameters used for caching VAddr addr; @@ -797,6 +834,9 @@ struct SurfaceParams { u32 layer_stride; u32 base_layer; } rt; + +private: + std::size_t InnerMemorySize(bool layer_only = false) const; }; }; // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index f4340a017..81ffb24e4 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -6,6 +6,7 @@ #include <set> #include <string> #include <string_view> +#include <unordered_set> #include <boost/optional.hpp> #include <fmt/format.h> @@ -30,8 +31,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 @@ -165,10 +164,11 @@ private: const ExitMethod jmp = Scan(target, end, labels); return exit_method = ParallelExit(no_jmp, jmp); } - case OpCode::Id::SSY: { - // The SSY instruction uses a similar encoding as the BRA instruction. + case OpCode::Id::SSY: + case OpCode::Id::PBK: { + // The SSY and PBK use a similar encoding as the BRA instruction. ASSERT_MSG(instr.bra.constant_buffer == 0, - "Constant buffer SSY is not supported"); + "Constant buffer branching is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); // Continue scanning for an exit method. @@ -277,7 +277,8 @@ public: GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations, const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix, const Tegra::Shader::Header& header) - : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header} { + : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header}, + fixed_pipeline_output_attributes_used{} { BuildRegisterList(); BuildInputList(); } @@ -376,11 +377,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, @@ -437,7 +482,12 @@ public: std::to_string(static_cast<u32>(attribute)) + ']' + GetSwizzle(elem) + " = " + src + ';'); } else { - shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + if (attribute == Attribute::Index::PointSize) { + fixed_pipeline_output_attributes_used.insert(attribute); + shader.AddLine(dest + " = " + src + ';'); + } else { + shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';'); + } } } } @@ -481,6 +531,7 @@ public: /// Add declarations. void GenerateDeclarations(const std::string& suffix) { + GenerateVertex(); GenerateRegisters(suffix); GenerateInternalFlags(); GenerateInputAttrs(); @@ -548,13 +599,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 = @@ -577,10 +621,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) - @@ -651,6 +691,20 @@ private: declarations.AddNewLine(); } + void GenerateVertex() { + if (stage != Maxwell3D::Regs::ShaderStage::Vertex) + return; + declarations.AddLine("out gl_PerVertex {"); + ++declarations.scope; + declarations.AddLine("vec4 gl_Position;"); + for (auto& o : fixed_pipeline_output_attributes_used) { + if (o == Attribute::Index::PointSize) + declarations.AddLine("float gl_PointSize;"); + } + --declarations.scope; + declarations.AddLine("};"); + } + /// Generates code representing a temporary (GPR) register. std::string GetRegister(const Register& reg, unsigned elem) { if (reg == Register::ZeroIndex) { @@ -804,6 +858,8 @@ private: /// Generates code representing the declaration name of an output attribute register. std::string GetOutputAttribute(Attribute::Index attribute) { switch (attribute) { + case Attribute::Index::PointSize: + return "gl_PointSize"; case Attribute::Index::Position: return "position"; default: @@ -838,6 +894,7 @@ private: const Maxwell3D::Regs::ShaderStage& stage; const std::string& suffix; const Tegra::Shader::Header& header; + std::unordered_set<Attribute::Index> fixed_pipeline_output_attributes_used; }; class GLSLGenerator { @@ -877,6 +934,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) { @@ -908,7 +978,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)); } @@ -1013,6 +1083,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. */ @@ -1154,27 +1259,27 @@ private: } /* - * Emits code to push the input target address to the SSY address stack, incrementing the stack + * Emits code to push the input target address to the flow address stack, incrementing the stack * top. */ - void EmitPushToSSYStack(u32 target) { + void EmitPushToFlowStack(u32 target) { shader.AddLine('{'); ++shader.scope; - shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;"); - shader.AddLine("ssy_stack_top++;"); + shader.AddLine("flow_stack[flow_stack_top] = " + std::to_string(target) + "u;"); + shader.AddLine("flow_stack_top++;"); --shader.scope; shader.AddLine('}'); } /* - * Emits code to pop an address from the SSY address stack, setting the jump address to the + * Emits code to pop an address from the flow address stack, setting the jump address to the * popped address and decrementing the stack top. */ - void EmitPopFromSSYStack() { + void EmitPopFromFlowStack() { shader.AddLine('{'); ++shader.scope; - shader.AddLine("ssy_stack_top--;"); - shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];"); + shader.AddLine("flow_stack_top--;"); + shader.AddLine("jmp_to = flow_stack[flow_stack_top];"); shader.AddLine("break;"); --shader.scope; shader.AddLine('}'); @@ -1186,9 +1291,29 @@ private: ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented"); + shader.AddLine("if (alpha_test[0] != 0) {"); + ++shader.scope; + // We start on the register containing the alpha value in the first RT. + u32 current_reg = 3; + for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets; + ++render_target) { + // TODO(Blinkhawk): verify the behavior of alpha testing on hardware when + // multiple render targets are used. + if (header.ps.IsColorComponentOutputEnabled(render_target, 0) || + header.ps.IsColorComponentOutputEnabled(render_target, 1) || + header.ps.IsColorComponentOutputEnabled(render_target, 2) || + header.ps.IsColorComponentOutputEnabled(render_target, 3)) { + shader.AddLine(fmt::format("if (!AlphaFunc({})) discard;", + regs.GetRegisterAsFloat(current_reg))); + current_reg += 4; + } + } + --shader.scope; + shader.AddLine('}'); + // Write the color outputs using the data in the shader registers, disabled // rendertargets/components are skipped in the register assignment. - u32 current_reg = 0; + current_reg = 0; for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets; ++render_target) { // TODO(Subv): Figure out how dual-source blending is configured in the Switch. @@ -1212,6 +1337,63 @@ private: } } + /// Unpacks a video instruction operand (e.g. VMAD). + std::string GetVideoOperand(const std::string& op, bool is_chunk, bool is_signed, + Tegra::Shader::VideoType 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 std::string zero = "0"; + + switch (type) { + case Tegra::Shader::VideoType::Size16_Low: + return '(' + op + " & 0xffff)"; + case Tegra::Shader::VideoType::Size16_High: + return '(' + op + " >> 16)"; + case Tegra::Shader::VideoType::Size32: + // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when + // this type is used (1 * 1 + 0 == 0x5b800000). Until a better + // explanation is found: assert. + UNIMPLEMENTED(); + return zero; + case Tegra::Shader::VideoType::Invalid: + UNREACHABLE_MSG("Invalid instruction encoding"); + return zero; + default: + UNREACHABLE(); + return zero; + } + }(); + + if (is_signed) { + return "int(" + value + ')'; + } + return value; + }; + + /// Gets the A operand for a video instruction. + std::string GetVideoOperandA(Instruction instr) { + return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr8, 0, false), + instr.video.is_byte_chunk_a != 0, instr.video.signed_a, + instr.video.type_a, instr.video.byte_height_a); + } + + /// Gets the B operand for a video instruction. + std::string GetVideoOperandB(Instruction instr) { + if (instr.video.use_register_b) { + return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.video.is_byte_chunk_b != 0, instr.video.signed_b, + instr.video.type_b, instr.video.byte_height_b); + } else { + return '(' + + std::to_string(instr.video.signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) + : instr.alu.GetImm20_16()) + + ')'; + } + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -1381,9 +1563,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 +1931,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 +2055,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: { @@ -2525,20 +2841,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) { @@ -2611,6 +2920,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); @@ -2689,33 +3043,24 @@ private: break; } case OpCode::Type::FloatSet: { - std::string op_a = instr.fset.neg_a ? "-" : ""; - op_a += regs.GetRegisterAsFloat(instr.gpr8); - - if (instr.fset.abs_a) { - op_a = "abs(" + op_a + ')'; - } + const std::string op_a = GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), + instr.fset.abs_a != 0, instr.fset.neg_a != 0); - 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. @@ -2771,6 +3116,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"); @@ -2979,16 +3368,32 @@ private: // The SSY opcode tells the GPU where to re-converge divergent execution paths, it // sets the target of the jump that the SYNC instruction will make. The SSY opcode // has a similar structure to the BRA opcode. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); + ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer flow is not supported"); + + const u32 target = offset + instr.bra.GetBranchTarget(); + EmitPushToFlowStack(target); + break; + } + case OpCode::Id::PBK: { + // PBK pushes to a stack the address where BRK will jump to. This shares stack with + // SSY but using SYNC on a PBK address will kill the shader execution. We don't + // emulate this because it's very unlikely a driver will emit such invalid shader. + ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer PBK is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); - EmitPushToSSYStack(target); + EmitPushToFlowStack(target); break; } case OpCode::Id::SYNC: { // The SYNC opcode jumps to the address previously set by the SSY opcode ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); - EmitPopFromSSYStack(); + EmitPopFromFlowStack(); + break; + } + case OpCode::Id::BRK: { + // The BRK opcode jumps to the address previously set by the PBK opcode + ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); + EmitPopFromFlowStack(); break; } case OpCode::Id::DEPBAR: { @@ -2998,87 +3403,51 @@ private: break; } case OpCode::Id::VMAD: { - const bool signed_a = instr.vmad.signed_a == 1; - const bool signed_b = instr.vmad.signed_b == 1; - const bool result_signed = signed_a || signed_b; - boost::optional<std::string> forced_result; - - auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed, - 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 std::string zero = "0"; - - switch (type) { - case Tegra::Shader::VmadType::Size16_Low: - return '(' + op + " & 0xffff)"; - case Tegra::Shader::VmadType::Size16_High: - return '(' + op + " >> 16)"; - case Tegra::Shader::VmadType::Size32: - // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when - // this type is used (1 * 1 + 0 == 0x5b800000). Until a better - // explanation is found: assert. - UNREACHABLE_MSG("Unimplemented"); - return zero; - case Tegra::Shader::VmadType::Invalid: - // Note(Rodrigo): This flag is invalid according to nvdisasm. From my - // testing (even though it's invalid) this makes the whole instruction - // assign zero to target register. - forced_result = boost::make_optional(zero); - return zero; - default: - UNREACHABLE(); - return zero; - } - }(); - - if (is_signed) { - return "int(" + value + ')'; - } - return value; - }; - - const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false), - instr.vmad.is_byte_chunk_a != 0, signed_a, - instr.vmad.type_a, instr.vmad.byte_height_a); - - std::string op_b; - if (instr.vmad.use_register_b) { - op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false), - instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b, - instr.vmad.byte_height_b); - } else { - op_b = '(' + - std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) - : instr.alu.GetImm20_16()) + - ')'; - } - + const bool result_signed = instr.video.signed_a == 1 || instr.video.signed_b == 1; + const std::string op_a = GetVideoOperandA(instr); + const std::string op_b = GetVideoOperandB(instr); const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed); - std::string result; - if (forced_result) { - result = *forced_result; - } else { - result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; + std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; - switch (instr.vmad.shr) { - case Tegra::Shader::VmadShr::Shr7: - result = '(' + result + " >> 7)"; - break; - case Tegra::Shader::VmadShr::Shr15: - result = '(' + result + " >> 15)"; - break; - } + switch (instr.vmad.shr) { + case Tegra::Shader::VmadShr::Shr7: + result = '(' + result + " >> 7)"; + break; + case Tegra::Shader::VmadShr::Shr15: + result = '(' + result + " >> 15)"; + break; } + regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1, instr.vmad.saturate == 1, 0, Register::Size::Word, instr.vmad.cc); break; } + case OpCode::Id::VSETP: { + const std::string op_a = GetVideoOperandA(instr); + const std::string op_b = GetVideoOperandB(instr); + + // We can't use the constant predicate as destination. + ASSERT(instr.vsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); + + const std::string second_pred = GetPredicateCondition(instr.vsetp.pred39, false); + + const std::string combiner = GetPredicateCombiner(instr.vsetp.op); + + const std::string predicate = GetPredicateComparison(instr.vsetp.cond, op_a, op_b); + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.vsetp.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + + if (instr.vsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.vsetp.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -3142,11 +3511,11 @@ private: labels.insert(subroutine.begin); shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); - // TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems - // unlikely that shaders will use 20 nested SSYs. - constexpr u32 SSY_STACK_SIZE = 20; - shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];"); - shader.AddLine("uint ssy_stack_top = 0u;"); + // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems + // unlikely that shaders will use 20 nested SSYs and PBKs. + constexpr u32 FLOW_STACK_SIZE = 20; + shader.AddLine("uint flow_stack[" + std::to_string(FLOW_STACK_SIZE) + "];"); + shader.AddLine("uint flow_stack_top = 0u;"); shader.AddLine("while (true) {"); ++shader.scope; @@ -3213,7 +3582,7 @@ private: // Declarations std::set<std::string> declr_predicates; -}; // namespace Decompiler +}; // namespace OpenGL::GLShader::Decompiler std::string GetCommonDeclarations() { return fmt::format("#define MAX_CONSTBUFFER_ELEMENTS {}\n", diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 1e5eb32df..dfb562706 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -19,14 +19,14 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) { out += Decompiler::GetCommonDeclarations(); out += R"( -out gl_PerVertex { - vec4 gl_Position; -}; + +layout (location = 0) out vec4 position; layout(std140) uniform vs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; )"; @@ -96,10 +96,14 @@ 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; uvec4 flip_stage; + uvec4 alpha_test; }; void main() { @@ -131,12 +135,39 @@ 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; uvec4 flip_stage; + uvec4 alpha_test; }; +bool AlphaFunc(in float value) { + float ref = uintBitsToFloat(alpha_test[2]); + switch (alpha_test[1]) { + case 1: + return false; + case 2: + return value < ref; + case 3: + return value == ref; + case 4: + return value <= ref; + case 5: + return value > ref; + case 6: + return value != ref; + case 7: + return value >= ref; + case 8: + return true; + default: + return false; + } +} + void main() { exec_fragment(); } @@ -145,4 +176,4 @@ void main() { out += program.first; return {out, program.second}; } -} // namespace OpenGL::GLShader
\ No newline at end of file +} // namespace OpenGL::GLShader 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/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 010857ec6..8b8869ecb 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -16,6 +16,17 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f; viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f; + u32 func = static_cast<u32>(regs.alpha_test_func); + // Normalize the gl variants of opCompare to be the same as the normal variants + u32 op_gl_variant_base = static_cast<u32>(Tegra::Engines::Maxwell3D::Regs::ComparisonOp::Never); + if (func >= op_gl_variant_base) { + func = func - op_gl_variant_base + 1U; + } + + alpha_test.enabled = regs.alpha_test_enabled; + alpha_test.func = func; + alpha_test.ref = regs.alpha_test_ref; + // We only assign the instance to the first component of the vector, the rest is just padding. instance_id[0] = state.current_instance; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index b3a191cf2..36fe1f04c 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -22,8 +22,14 @@ struct MaxwellUniformData { alignas(16) GLvec4 viewport_flip; alignas(16) GLuvec4 instance_id; alignas(16) GLuvec4 flip_stage; + struct alignas(16) { + GLuint enabled; + GLuint func; + GLfloat ref; + GLuint padding; + } alpha_test; }; -static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 64, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 1fe26a2a9..ba6c6919a 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -21,6 +21,8 @@ OpenGLState::OpenGLState() { depth.test_enabled = false; depth.test_func = GL_LESS; depth.write_mask = GL_TRUE; + depth.depth_range_near = 0.0f; + depth.depth_range_far = 1.0f; color_mask.red_enabled = GL_TRUE; color_mask.green_enabled = GL_TRUE; @@ -119,6 +121,12 @@ void OpenGLState::Apply() const { glDepthMask(depth.write_mask); } + // Depth range + if (depth.depth_range_near != cur_state.depth.depth_range_near || + depth.depth_range_far != cur_state.depth.depth_range_far) { + glDepthRange(depth.depth_range_near, depth.depth_range_far); + } + // Color mask if (color_mask.red_enabled != cur_state.color_mask.red_enabled || color_mask.green_enabled != cur_state.color_mask.green_enabled || diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index dc21a2ee3..daf7eb533 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -42,9 +42,11 @@ public: } cull; struct { - bool test_enabled; // GL_DEPTH_TEST - GLenum test_func; // GL_DEPTH_FUNC - GLboolean write_mask; // GL_DEPTH_WRITEMASK + bool test_enabled; // GL_DEPTH_TEST + GLenum test_func; // GL_DEPTH_FUNC + GLboolean write_mask; // GL_DEPTH_WRITEMASK + GLfloat depth_range_near; // GL_DEPTH_RANGE + GLfloat depth_range_far; // GL_DEPTH_RANGE } depth; struct { 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()); |
