From 2e5b5c2358caaf8dfd403a30924d49c31aa962a0 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 11 Jul 2019 20:09:53 -0300 Subject: [PATCH 01/11] gl_rasterizer: Split SetupTextures --- .../renderer_opengl/gl_rasterizer.cpp | 50 +++++++++++-------- .../renderer_opengl/gl_rasterizer.h | 10 +++- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 01d89f47d..0f0902259 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -331,7 +331,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { const auto stage_enum = static_cast(stage); SetupDrawConstBuffers(stage_enum, shader); SetupDrawGlobalMemory(stage_enum, shader); - const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)}; + const auto texture_buffer_usage{SetupDrawTextures(stage_enum, shader, base_bindings)}; const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage}; const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant); @@ -981,8 +981,9 @@ void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entr bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast(size)); } -TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, - BaseBindings base_bindings) { +TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stage, + const Shader& shader, + BaseBindings base_bindings) { MICROPROFILE_SCOPE(OpenGL_Texture); const auto& gpu = system.GPU(); const auto& maxwell3d = gpu.Maxwell3D(); @@ -1004,30 +1005,39 @@ TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, c } else { texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); } - const u32 current_bindpoint = base_bindings.sampler + bindpoint; - auto& unit{state.texture_units[current_bindpoint]}; - unit.sampler = sampler_cache.GetSampler(texture.tsc); - - if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) { - if (view->GetSurfaceParams().IsBuffer()) { - // Record that this texture is a texture buffer. - texture_buffer_usage.set(bindpoint); - } else { - // Apply swizzle to textures that are not buffers. - view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, - texture.tic.w_source); - } - state.texture_units[current_bindpoint].texture = view->GetTexture(); - } else { - // Can occur when texture addr is null or its memory is unmapped/invalid - unit.texture = 0; + if (SetupTexture(shader, base_bindings.sampler + bindpoint, texture, entry)) { + texture_buffer_usage.set(bindpoint); } } return texture_buffer_usage; } +bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding, + const Tegra::Texture::FullTextureInfo& texture, + const GLShader::SamplerEntry& entry) { + auto& unit{state.texture_units[binding]}; + unit.sampler = sampler_cache.GetSampler(texture.tsc); + + const auto view = texture_cache.GetTextureSurface(texture, entry); + if (!view) { + // Can occur when texture addr is null or its memory is unmapped/invalid + unit.texture = 0; + return false; + } + unit.texture = view->GetTexture(); + + if (view->GetSurfaceParams().IsBuffer()) { + return true; + } + + // Apply swizzle to textures that are not buffers. + view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, + texture.tic.w_source); + return false; +} + void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { const auto& regs = system.GPU().Maxwell3D().regs; const bool geometry_shaders_enabled = diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 9d20a4fbf..23ab7aff0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -32,6 +32,7 @@ #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/gl_texture_cache.h" #include "video_core/renderer_opengl/utils.h" +#include "video_core/textures/texture.h" namespace Core { class System; @@ -137,8 +138,13 @@ private: /// Configures the current textures to use for the draw command. Returns shaders texture buffer /// usage. - TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, - const Shader& shader, BaseBindings base_bindings); + TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, + const Shader& shader, BaseBindings base_bindings); + + /// Configures a texture. Returns true when the texture is a texture buffer. + bool SetupTexture(const Shader& shader, u32 binding, + const Tegra::Texture::FullTextureInfo& texture, + const GLShader::SamplerEntry& entry); /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); From 3a450c1395cdb8b4f73687f8c49648e9190fc3a0 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 11 Jul 2019 21:54:07 -0300 Subject: [PATCH 02/11] kepler_compute: Implement texture queries --- src/video_core/engines/kepler_compute.cpp | 53 +++++++++++++++++++ src/video_core/engines/kepler_compute.h | 23 ++++++-- .../renderer_opengl/gl_rasterizer.cpp | 22 +++++++- .../renderer_opengl/gl_rasterizer.h | 2 + src/video_core/shader/node.h | 4 ++ 5 files changed, 99 insertions(+), 5 deletions(-) diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp index 08586d33c..63d449135 100644 --- a/src/video_core/engines/kepler_compute.cpp +++ b/src/video_core/engines/kepler_compute.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -49,6 +50,33 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) { } } +Tegra::Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const { + const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value(); + ASSERT(cbuf_mask[regs.tex_cb_index]); + + const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index]; + ASSERT(texinfo.Address() != 0); + + const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle); + ASSERT(address < texinfo.Address() + texinfo.size); + + const Texture::TextureHandle tex_handle{memory_manager.Read(address)}; + return GetTextureInfo(tex_handle, offset); +} + +Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHandle tex_handle, + std::size_t offset) const { + return Texture::FullTextureInfo{static_cast(offset), GetTICEntry(tex_handle.tic_id), + GetTSCEntry(tex_handle.tsc_id)}; +} + +u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const { + const auto& buffer = launch_description.const_buffer_config[const_buffer]; + u32 result; + std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32)); + return result; +} + void KeplerCompute::ProcessLaunch() { const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, @@ -60,4 +88,29 @@ void KeplerCompute::ProcessLaunch() { rasterizer.DispatchCompute(code_addr); } +Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const { + const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)}; + + Texture::TICEntry tic_entry; + memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry)); + + const auto r_type{tic_entry.r_type.Value()}; + const auto g_type{tic_entry.g_type.Value()}; + const auto b_type{tic_entry.b_type.Value()}; + const auto a_type{tic_entry.a_type.Value()}; + + // TODO(Subv): Different data types for separate components are not supported + DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type); + + return tic_entry; +} + +Texture::TSCEntry KeplerCompute::GetTSCEntry(u32 tsc_index) const { + const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)}; + + Texture::TSCEntry tsc_entry; + memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry)); + return tsc_entry; +} + } // namespace Tegra::Engines diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h index 6a3309a2c..90cf650d2 100644 --- a/src/video_core/engines/kepler_compute.h +++ b/src/video_core/engines/kepler_compute.h @@ -12,6 +12,7 @@ #include "common/common_types.h" #include "video_core/engines/engine_upload.h" #include "video_core/gpu.h" +#include "video_core/textures/texture.h" namespace Core { class System; @@ -111,7 +112,7 @@ public: INSERT_PADDING_WORDS(0x3FE); - u32 texture_const_buffer_index; + u32 tex_cb_index; INSERT_PADDING_WORDS(0x374); }; @@ -149,7 +150,7 @@ public: union { BitField<0, 8, u32> const_buffer_enable_mask; BitField<29, 2, u32> cache_layout; - } memory_config; + }; INSERT_PADDING_WORDS(0x8); @@ -194,6 +195,14 @@ public: /// Write the value to the register identified by method. void CallMethod(const GPU::MethodCall& method_call); + Tegra::Texture::FullTextureInfo GetTexture(std::size_t offset) const; + + /// Given a Texture Handle, returns the TSC and TIC entries. + Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle, + std::size_t offset) const; + + u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const; + private: Core::System& system; VideoCore::RasterizerInterface& rasterizer; @@ -201,6 +210,12 @@ private: Upload::State upload_state; void ProcessLaunch(); + + /// Retrieves information about a specific TIC entry from the TIC buffer. + Texture::TICEntry GetTICEntry(u32 tic_index) const; + + /// Retrieves information about a specific TSC entry from the TSC buffer. + Texture::TSCEntry GetTSCEntry(u32 tsc_index) const; }; #define ASSERT_REG_POSITION(field_name, position) \ @@ -218,12 +233,12 @@ ASSERT_REG_POSITION(launch, 0xAF); ASSERT_REG_POSITION(tsc, 0x557); ASSERT_REG_POSITION(tic, 0x55D); ASSERT_REG_POSITION(code_loc, 0x582); -ASSERT_REG_POSITION(texture_const_buffer_index, 0x982); +ASSERT_REG_POSITION(tex_cb_index, 0x982); ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); -ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14); +ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14); ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); #undef ASSERT_REG_POSITION diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 0f0902259..5375ab9e0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -801,6 +801,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { } auto kernel = shader_cache.GetComputeKernel(code_addr); + SetupComputeImages(kernel); + const auto [program, next_bindings] = kernel->GetProgramHandle({}); state.draw.shader_program = program; state.draw.program_pipeline = 0; @@ -922,7 +924,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) { const auto& launch_desc = system.GPU().KeplerCompute().launch_description; for (const auto& entry : kernel->GetShaderEntries().const_buffers) { const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; - const std::bitset<8> mask = launch_desc.memory_config.const_buffer_enable_mask.Value(); + const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value(); Tegra::Engines::ConstBufferInfo buffer; buffer.address = config.Address(); buffer.size = config.size; @@ -1038,6 +1040,24 @@ bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding, return false; } +void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { + const auto& compute = system.GPU().KeplerCompute(); + const auto& entries = shader->GetShaderEntries().images; + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + const auto texture = [&]() { + if (!entry.IsBindless()) { + return compute.GetTexture(entry.GetOffset()); + } + const auto cbuf = entry.GetBindlessCBuf(); + Tegra::Texture::TextureHandle tex_handle; + tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); + UNIMPLEMENTED(); + } +} + void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { const auto& regs = system.GPU().Maxwell3D().regs; const bool geometry_shaders_enabled = diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 23ab7aff0..6fa1b7ec4 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -146,6 +146,8 @@ private: const Tegra::Texture::FullTextureInfo& texture, const GLShader::SamplerEntry& entry); + void SetupComputeImages(const Shader& shader); + /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 5db9313c4..0397f4c6e 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -303,6 +303,10 @@ public: return is_bindless; } + std::pair GetBindlessCBuf() const { + return {static_cast(offset >> 32), static_cast(offset)}; + } + bool operator<(const Image& rhs) const { return std::tie(offset, index, type, is_bindless) < std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); From 2424eefad20b018bed72a0427cdeeabb08bea7b2 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 11 Jul 2019 21:59:59 -0300 Subject: [PATCH 03/11] texture_cache: Pass TIC to texture cache --- .../renderer_opengl/gl_rasterizer.cpp | 2 +- .../texture_cache/surface_params.cpp | 37 +++++++++---------- src/video_core/texture_cache/surface_params.h | 5 +-- src/video_core/texture_cache/texture_cache.h | 8 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 5375ab9e0..8a59b86e3 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1022,7 +1022,7 @@ bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding, auto& unit{state.texture_units[binding]}; unit.sampler = sampler_cache.GetSampler(texture.tsc); - const auto view = texture_cache.GetTextureSurface(texture, entry); + const auto view = texture_cache.GetImageSurface(texture.tic, entry); if (!view) { // Can occur when texture addr is null or its memory is unmapped/invalid unit.texture = 0; diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp index fd5472451..2f8bd399c 100644 --- a/src/video_core/texture_cache/surface_params.cpp +++ b/src/video_core/texture_cache/surface_params.cpp @@ -61,18 +61,17 @@ constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { } } // Anonymous namespace -SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config, - const VideoCommon::Shader::Sampler& entry) { +SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry) { SurfaceParams params; - params.is_tiled = config.tic.IsTiled(); - params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); - params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, - params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, - params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, - params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; - params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), - params.srgb_conversion); + params.is_tiled = tic.IsTiled(); + params.srgb_conversion = tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); params.type = GetFormatType(params.pixel_format); if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { switch (params.pixel_format) { @@ -92,25 +91,25 @@ SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, } params.type = GetFormatType(params.pixel_format); } - params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); + params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); params.type = GetFormatType(params.pixel_format); // TODO: on 1DBuffer we should use the tic info. - if (!config.tic.IsBuffer()) { + if (!tic.IsBuffer()) { params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray()); - params.width = config.tic.Width(); - params.height = config.tic.Height(); - params.depth = config.tic.Depth(); - params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); + params.width = tic.Width(); + params.height = tic.Height(); + params.depth = tic.Depth(); + params.pitch = params.is_tiled ? 0 : tic.Pitch(); if (params.target == SurfaceTarget::TextureCubemap || params.target == SurfaceTarget::TextureCubeArray) { params.depth *= 6; } - params.num_levels = config.tic.max_mip_level + 1; + params.num_levels = tic.max_mip_level + 1; params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); params.is_layered = params.IsLayered(); } else { params.target = SurfaceTarget::TextureBuffer; - params.width = config.tic.Width(); + params.width = tic.Width(); params.pitch = params.width * params.GetBytesPerPixel(); params.height = 1; params.depth = 1; diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index e7ef66ee2..ee2efa594 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -23,9 +23,8 @@ using VideoCore::Surface::SurfaceCompression; class SurfaceParams { public: /// Creates SurfaceCachedParams from a texture configuration. - static SurfaceParams CreateForTexture(Core::System& system, - const Tegra::Texture::FullTextureInfo& config, - const VideoCommon::Shader::Sampler& entry); + static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry); /// Creates SurfaceCachedParams for a depth buffer configuration. static SurfaceParams CreateForDepthBuffer( diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 2ec0203d1..623cce068 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -89,14 +89,14 @@ public: } } - TView GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, - const VideoCommon::Shader::Sampler& entry) { + TView GetImageSurface(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry) { std::lock_guard lock{mutex}; - const auto gpu_addr{config.tic.Address()}; + const auto gpu_addr{tic.Address()}; if (!gpu_addr) { return {}; } - const auto params{SurfaceParams::CreateForTexture(system, config, entry)}; + const auto params{SurfaceParams::CreateForImage(tic, entry)}; const auto [surface, view] = GetSurface(gpu_addr, params, true, false); if (guard_samplers) { sampled_textures.push_back(surface); From 5edf24b51025fef4d78e5f9d4038267e472b2f55 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Fri, 12 Jul 2019 02:00:04 -0300 Subject: [PATCH 04/11] gl_state: Add support for glBindImageTextures --- src/video_core/renderer_opengl/gl_state.cpp | 21 +++++++++++++++++++++ src/video_core/renderer_opengl/gl_state.h | 3 +++ 2 files changed, 24 insertions(+) diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index f4777d0b0..a38f88182 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -545,6 +545,26 @@ void OpenGLState::ApplySamplers() const { } } +void OpenGLState::ApplyImages() const { + bool has_delta{}; + std::size_t first{}; + std::size_t last{}; + for (std::size_t i = 0; i < std::size(images); ++i) { + if (!UpdateValue(cur_state.images[i], images[i])) { + continue; + } + if (!has_delta) { + first = i; + has_delta = true; + } + last = i; + } + if (has_delta) { + glBindImageTextures(static_cast(first), static_cast(last - first + 1), + images.data() + first); + } +} + void OpenGLState::Apply() { MICROPROFILE_SCOPE(OpenGL_State); ApplyFramebufferState(); @@ -576,6 +596,7 @@ void OpenGLState::Apply() { ApplyLogicOp(); ApplyTextures(); ApplySamplers(); + ApplyImages(); if (dirty.polygon_offset) { ApplyPolygonOffset(); dirty.polygon_offset = false; diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index fdf9a8a12..9748d60e2 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -134,6 +134,8 @@ public: }; std::array texture_units; + std::array images{}; + struct { GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING @@ -220,6 +222,7 @@ public: void ApplyLogicOp() const; void ApplyTextures() const; void ApplySamplers() const; + void ApplyImages() const; void ApplyDepthClamp() const; void ApplyPolygonOffset() const; void ApplyAlphaTest() const; From 6170337001cc11deabd57023a548c44de4242e9d Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Fri, 12 Jul 2019 02:01:27 -0300 Subject: [PATCH 05/11] gl_rasterizer: Implement image bindings --- src/video_core/engines/maxwell_3d.h | 1 + .../renderer_opengl/gl_rasterizer.cpp | 9 +- .../texture_cache/surface_params.cpp | 105 +++++++++++++----- src/video_core/texture_cache/surface_params.h | 6 +- src/video_core/texture_cache/texture_cache.h | 17 ++- 5 files changed, 106 insertions(+), 32 deletions(-) diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 0184342a0..3b3c82f41 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -62,6 +62,7 @@ public: static constexpr std::size_t NumVertexAttributes = 32; static constexpr std::size_t NumVaryings = 31; static constexpr std::size_t NumTextureSamplers = 32; + static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number static constexpr std::size_t NumClipDistances = 8; static constexpr std::size_t MaxShaderProgram = 6; static constexpr std::size_t MaxShaderStage = 5; diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 8a59b86e3..6636b3c74 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1022,7 +1022,7 @@ bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding, auto& unit{state.texture_units[binding]}; unit.sampler = sampler_cache.GetSampler(texture.tsc); - const auto view = texture_cache.GetImageSurface(texture.tic, entry); + const auto view = texture_cache.GetTextureSurface(texture.tic, entry); if (!view) { // Can occur when texture addr is null or its memory is unmapped/invalid unit.texture = 0; @@ -1054,7 +1054,12 @@ void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); return compute.GetTextureInfo(tex_handle, entry.GetOffset()); }(); - UNIMPLEMENTED(); + const auto view = texture_cache.GetImageSurface(texture.tic, entry); + if (!view) { + state.images[bindpoint] = 0; + continue; + } + state.images[bindpoint] = view->GetTexture(); } } diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp index 2f8bd399c..1e4d3fb79 100644 --- a/src/video_core/texture_cache/surface_params.cpp +++ b/src/video_core/texture_cache/surface_params.cpp @@ -24,45 +24,53 @@ using VideoCore::Surface::SurfaceTarget; using VideoCore::Surface::SurfaceTargetFromTextureType; using VideoCore::Surface::SurfaceType; -SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { +namespace { + +SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { switch (type) { - case Tegra::Shader::TextureType::Texture1D: { - if (is_array) - return SurfaceTarget::Texture1DArray; - else - return SurfaceTarget::Texture1D; - } - case Tegra::Shader::TextureType::Texture2D: { - if (is_array) - return SurfaceTarget::Texture2DArray; - else - return SurfaceTarget::Texture2D; - } - case Tegra::Shader::TextureType::Texture3D: { + case Tegra::Shader::TextureType::Texture1D: + return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D; + case Tegra::Shader::TextureType::Texture2D: + return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; + case Tegra::Shader::TextureType::Texture3D: ASSERT(!is_array); return SurfaceTarget::Texture3D; - } - case Tegra::Shader::TextureType::TextureCube: { - if (is_array) - return SurfaceTarget::TextureCubeArray; - else - return SurfaceTarget::TextureCubemap; - } - default: { + case Tegra::Shader::TextureType::TextureCube: + return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap; + default: UNREACHABLE(); return SurfaceTarget::Texture2D; } +} + +SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) { + switch (type) { + case Tegra::Shader::ImageType::Texture1D: + return SurfaceTarget::Texture1D; + case Tegra::Shader::ImageType::TextureBuffer: + return SurfaceTarget::TextureBuffer; + case Tegra::Shader::ImageType::Texture1DArray: + return SurfaceTarget::Texture1DArray; + case Tegra::Shader::ImageType::Texture2D: + return SurfaceTarget::Texture2D; + case Tegra::Shader::ImageType::Texture2DArray: + return SurfaceTarget::Texture2DArray; + case Tegra::Shader::ImageType::Texture3D: + return SurfaceTarget::Texture3D; + default: + UNREACHABLE(); + return SurfaceTarget::Texture2D; } } -namespace { constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); } + } // Anonymous namespace -SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Sampler& entry) { +SurfaceParams SurfaceParams::CreateForTexture(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry) { SurfaceParams params; params.is_tiled = tic.IsTiled(); params.srgb_conversion = tic.IsSrgbConversionEnabled(); @@ -94,8 +102,17 @@ SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); params.type = GetFormatType(params.pixel_format); // TODO: on 1DBuffer we should use the tic info. - if (!tic.IsBuffer()) { - params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray()); + if (tic.IsBuffer()) { + params.target = SurfaceTarget::TextureBuffer; + params.width = tic.Width(); + params.pitch = params.width * params.GetBytesPerPixel(); + params.height = 1; + params.depth = 1; + params.num_levels = 1; + params.emulated_levels = 1; + params.is_layered = false; + } else { + params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray()); params.width = tic.Width(); params.height = tic.Height(); params.depth = tic.Depth(); @@ -107,7 +124,27 @@ SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, params.num_levels = tic.max_mip_level + 1; params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); params.is_layered = params.IsLayered(); - } else { + } + return params; +} + +SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Image& entry) { + SurfaceParams params; + params.is_tiled = tic.IsTiled(); + params.srgb_conversion = tic.IsSrgbConversionEnabled(); + params.block_width = params.is_tiled ? tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion); + params.type = GetFormatType(params.pixel_format); + params.component_type = ComponentTypeFromTexture(tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + params.target = ImageTypeToSurfaceTarget(entry.GetType()); + // TODO: on 1DBuffer we should use the tic info. + if (tic.IsBuffer()) { params.target = SurfaceTarget::TextureBuffer; params.width = tic.Width(); params.pitch = params.width * params.GetBytesPerPixel(); @@ -116,6 +153,18 @@ SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic, params.num_levels = 1; params.emulated_levels = 1; params.is_layered = false; + } else { + params.width = tic.Width(); + params.height = tic.Height(); + params.depth = tic.Depth(); + params.pitch = params.is_tiled ? 0 : tic.Pitch(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.num_levels = tic.max_mip_level + 1; + params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); + params.is_layered = params.IsLayered(); } return params; } diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index ee2efa594..1011a4d8e 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -23,8 +23,12 @@ using VideoCore::Surface::SurfaceCompression; class SurfaceParams { public: /// Creates SurfaceCachedParams from a texture configuration. + static SurfaceParams CreateForTexture(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry); + + /// Creates SurfaceCachedParams from an image configuration. static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Sampler& entry); + const VideoCommon::Shader::Image& entry); /// Creates SurfaceCachedParams for a depth buffer configuration. static SurfaceParams CreateForDepthBuffer( diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 623cce068..877c6635d 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -89,8 +89,23 @@ public: } } + TView GetTextureSurface(const Tegra::Texture::TICEntry& tic, + const VideoCommon::Shader::Sampler& entry) { + std::lock_guard lock{mutex}; + const auto gpu_addr{tic.Address()}; + if (!gpu_addr) { + return {}; + } + const auto params{SurfaceParams::CreateForTexture(tic, entry)}; + const auto [surface, view] = GetSurface(gpu_addr, params, true, false); + if (guard_samplers) { + sampled_textures.push_back(surface); + } + return view; + } + TView GetImageSurface(const Tegra::Texture::TICEntry& tic, - const VideoCommon::Shader::Sampler& entry) { + const VideoCommon::Shader::Image& entry) { std::lock_guard lock{mutex}; const auto gpu_addr{tic.Address()}; if (!gpu_addr) { From 04cdecb7a114471fec50deab86bcd160ec85feb4 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 11 Jul 2019 01:32:12 -0300 Subject: [PATCH 06/11] gl_state: Split textures and samplers into two arrays --- .../renderer_opengl/gl_rasterizer.cpp | 9 +- src/video_core/renderer_opengl/gl_state.cpp | 99 ++++++------------- src/video_core/renderer_opengl/gl_state.h | 18 +--- .../renderer_opengl/renderer_opengl.cpp | 4 +- 4 files changed, 39 insertions(+), 91 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 6636b3c74..818e71754 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -991,7 +991,7 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag const auto& maxwell3d = gpu.Maxwell3D(); const auto& entries = shader->GetShaderEntries().samplers; - ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units), + ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.textures), "Exceeded the number of active textures."); TextureBufferUsage texture_buffer_usage{0}; @@ -1019,16 +1019,15 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding, const Tegra::Texture::FullTextureInfo& texture, const GLShader::SamplerEntry& entry) { - auto& unit{state.texture_units[binding]}; - unit.sampler = sampler_cache.GetSampler(texture.tsc); + state.samplers[binding] = sampler_cache.GetSampler(texture.tsc); const auto view = texture_cache.GetTextureSurface(texture.tic, entry); if (!view) { // Can occur when texture addr is null or its memory is unmapped/invalid - unit.texture = 0; + state.textures[binding] = 0; return false; } - unit.texture = view->GetTexture(); + state.textures[binding] = view->GetTexture(); if (view->GetSurfaceParams().IsBuffer()) { return true; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index a38f88182..6eabf4fac 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -34,6 +34,25 @@ bool UpdateTie(T1 current_value, const T2 new_value) { return changed; } +template +std::optional> UpdateArray(T& current_values, const T& new_values) { + std::optional first; + std::size_t last; + for (std::size_t i = 0; i < std::size(current_values); ++i) { + if (!UpdateValue(current_values[i], new_values[i])) { + continue; + } + if (!first) { + first = i; + } + last = i; + } + if (!first) { + return std::nullopt; + } + return std::make_pair(static_cast(*first), static_cast(last - *first + 1)); +} + void Enable(GLenum cap, bool enable) { if (enable) { glEnable(cap); @@ -134,10 +153,6 @@ OpenGLState::OpenGLState() { logic_op.enabled = false; logic_op.operation = GL_COPY; - for (auto& texture_unit : texture_units) { - texture_unit.Reset(); - } - draw.read_framebuffer = 0; draw.draw_framebuffer = 0; draw.vertex_array = 0; @@ -496,72 +511,20 @@ void OpenGLState::ApplyAlphaTest() const { } void OpenGLState::ApplyTextures() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - std::array textures; - - for (std::size_t i = 0; i < std::size(texture_units); ++i) { - const auto& texture_unit = texture_units[i]; - auto& cur_state_texture_unit = cur_state.texture_units[i]; - textures[i] = texture_unit.texture; - if (cur_state_texture_unit.texture == textures[i]) { - continue; - } - cur_state_texture_unit.texture = textures[i]; - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; - } - if (has_delta) { - glBindTextures(static_cast(first), static_cast(last - first + 1), - textures.data() + first); + if (const auto update = UpdateArray(cur_state.textures, textures)) { + glBindTextures(update->first, update->second, textures.data() + update->first); } } void OpenGLState::ApplySamplers() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - std::array samplers; - - for (std::size_t i = 0; i < std::size(samplers); ++i) { - samplers[i] = texture_units[i].sampler; - if (cur_state.texture_units[i].sampler == texture_units[i].sampler) { - continue; - } - cur_state.texture_units[i].sampler = texture_units[i].sampler; - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; - } - if (has_delta) { - glBindSamplers(static_cast(first), static_cast(last - first + 1), - samplers.data() + first); + if (const auto update = UpdateArray(cur_state.samplers, samplers)) { + glBindSamplers(update->first, update->second, samplers.data() + update->first); } } void OpenGLState::ApplyImages() const { - bool has_delta{}; - std::size_t first{}; - std::size_t last{}; - for (std::size_t i = 0; i < std::size(images); ++i) { - if (!UpdateValue(cur_state.images[i], images[i])) { - continue; - } - if (!has_delta) { - first = i; - has_delta = true; - } - last = i; - } - if (has_delta) { - glBindImageTextures(static_cast(first), static_cast(last - first + 1), - images.data() + first); + if (const auto update = UpdateArray(cur_state.images, images)) { + glBindImageTextures(update->first, update->second, images.data() + update->first); } } @@ -627,18 +590,18 @@ void OpenGLState::EmulateViewportWithScissor() { } OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { - for (auto& unit : texture_units) { - if (unit.texture == handle) { - unit.Unbind(); + for (auto& texture : textures) { + if (texture == handle) { + texture = 0; } } return *this; } OpenGLState& OpenGLState::ResetSampler(GLuint handle) { - for (auto& unit : texture_units) { - if (unit.sampler == handle) { - unit.sampler = 0; + for (auto& sampler : samplers) { + if (sampler == handle) { + sampler = 0; } } return *this; diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 9748d60e2..949b13051 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -118,22 +118,8 @@ public: GLenum operation; } logic_op; - // 3 texture units - one for each that is used in PICA fragment shader emulation - struct TextureUnit { - GLuint texture; // GL_TEXTURE_BINDING_2D - GLuint sampler; // GL_SAMPLER_BINDING - - void Unbind() { - texture = 0; - } - - void Reset() { - Unbind(); - sampler = 0; - } - }; - std::array texture_units; - + std::array textures{}; + std::array samplers{}; std::array images{}; struct { diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index af9684839..839178152 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -342,7 +342,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), }}; - state.texture_units[0].texture = screen_info.display_texture; + state.textures[0] = screen_info.display_texture; // Workaround brigthness problems in SMO by enabling sRGB in the final output // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); @@ -352,7 +352,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x, glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // Restore default state state.framebuffer_srgb.enabled = false; - state.texture_units[0].texture = 0; + state.textures[0] = 0; state.AllDirty(); state.Apply(); // Clear sRGB state for the next frame From 954fc02fdd639a3e83ade8776d23b50559cd2f73 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Fri, 12 Jul 2019 02:17:18 -0300 Subject: [PATCH 07/11] gl_rasterizer: Minor code changes --- .../renderer_opengl/gl_rasterizer.cpp | 43 +++++++++++-------- .../renderer_opengl/gl_rasterizer.h | 8 +++- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 818e71754..5454bacb0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -998,17 +998,17 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry = entries[bindpoint]; - Tegra::Texture::FullTextureInfo texture; - if (entry.IsBindless()) { + const auto texture = [&]() { + if (!entry.IsBindless()) { + return maxwell3d.GetStageTexture(stage, entry.GetOffset()); + } const auto cbuf = entry.GetBindlessCBuf(); Tegra::Texture::TextureHandle tex_handle; tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second); - texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); - } else { - texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); - } + return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); - if (SetupTexture(shader, base_bindings.sampler + bindpoint, texture, entry)) { + if (SetupTexture(base_bindings.sampler + bindpoint, texture, entry)) { texture_buffer_usage.set(bindpoint); } } @@ -1016,8 +1016,7 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag return texture_buffer_usage; } -bool RasterizerOpenGL::SetupTexture(const Shader& shader, u32 binding, - const Tegra::Texture::FullTextureInfo& texture, +bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, const GLShader::SamplerEntry& entry) { state.samplers[binding] = sampler_cache.GetSampler(texture.tsc); @@ -1044,24 +1043,32 @@ void RasterizerOpenGL::SetupComputeImages(const Shader& shader) { const auto& entries = shader->GetShaderEntries().images; for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& entry = entries[bindpoint]; - const auto texture = [&]() { + const auto tic = [&]() { if (!entry.IsBindless()) { - return compute.GetTexture(entry.GetOffset()); + return compute.GetTexture(entry.GetOffset()).tic; } const auto cbuf = entry.GetBindlessCBuf(); Tegra::Texture::TextureHandle tex_handle; tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); - return compute.GetTextureInfo(tex_handle, entry.GetOffset()); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic; }(); - const auto view = texture_cache.GetImageSurface(texture.tic, entry); - if (!view) { - state.images[bindpoint] = 0; - continue; - } - state.images[bindpoint] = view->GetTexture(); + SetupImage(bindpoint, tic, entry); } } +void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, + const GLShader::ImageEntry& entry) { + const auto view = texture_cache.GetImageSurface(tic, entry); + if (!view) { + state.images[binding] = 0; + return; + } + if (!tic.IsBuffer()) { + view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source); + } + state.images[binding] = view->GetTexture(); +} + void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { const auto& regs = system.GPU().Maxwell3D().regs; const bool geometry_shaders_enabled = diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 6fa1b7ec4..35265b4a2 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -142,12 +142,16 @@ private: const Shader& shader, BaseBindings base_bindings); /// Configures a texture. Returns true when the texture is a texture buffer. - bool SetupTexture(const Shader& shader, u32 binding, - const Tegra::Texture::FullTextureInfo& texture, + bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, const GLShader::SamplerEntry& entry); + /// Configures images in a compute shader. void SetupComputeImages(const Shader& shader); + /// Configures an image. + void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic, + const GLShader::ImageEntry& entry); + /// Syncs the viewport and depth range to match the guest state void SyncViewport(OpenGLState& current_state); From 80ec2feee8731d661bc999c9f057159ec6286043 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Wed, 17 Jul 2019 21:50:21 -0300 Subject: [PATCH 08/11] gl_rasterizer: Add samplers to compute dispatches --- .../renderer_opengl/gl_rasterizer.cpp | 36 +++++++++++++++++-- .../renderer_opengl/gl_rasterizer.h | 3 ++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 5454bacb0..fd4753af7 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -801,9 +801,11 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { } auto kernel = shader_cache.GetComputeKernel(code_addr); + ProgramVariant variant; + variant.texture_buffer_usage = SetupComputeTextures(kernel); SetupComputeImages(kernel); - const auto [program, next_bindings] = kernel->GetProgramHandle({}); + const auto [program, next_bindings] = kernel->GetProgramHandle(variant); state.draw.shader_program = program; state.draw.program_pipeline = 0; @@ -818,8 +820,6 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { SetupComputeConstBuffers(kernel); SetupComputeGlobalMemory(kernel); - // TODO(Rodrigo): Bind images and samplers - buffer_cache.Unmap(); bind_ubo_pushbuffer.Bind(); @@ -1016,6 +1016,36 @@ TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stag return texture_buffer_usage; } +TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) { + MICROPROFILE_SCOPE(OpenGL_Texture); + const auto& compute = system.GPU().KeplerCompute(); + const auto& entries = kernel->GetShaderEntries().samplers; + + ASSERT_MSG(entries.size() <= std::size(state.textures), + "Exceeded the number of active textures."); + + TextureBufferUsage texture_buffer_usage{0}; + + for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry = entries[bindpoint]; + const auto texture = [&]() { + if (!entry.IsBindless()) { + return compute.GetTexture(entry.GetOffset()); + } + const auto cbuf = entry.GetBindlessCBuf(); + Tegra::Texture::TextureHandle tex_handle; + tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second); + return compute.GetTextureInfo(tex_handle, entry.GetOffset()); + }(); + + if (SetupTexture(bindpoint, texture, entry)) { + texture_buffer_usage.set(bindpoint); + } + } + + return texture_buffer_usage; +} + bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, const GLShader::SamplerEntry& entry) { state.samplers[binding] = sampler_cache.GetSampler(texture.tsc); diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 35265b4a2..eada752e0 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -141,6 +141,9 @@ private: TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, BaseBindings base_bindings); + /// Configures the textures used in a compute shader. Returns texture buffer usage. + TextureBufferUsage SetupComputeTextures(const Shader& kernel); + /// Configures a texture. Returns true when the texture is a texture buffer. bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture, const GLShader::SamplerEntry& entry); From 322d0200c80e36cb97b0d0d79f0f3e9eaa2f96af Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 18 Jul 2019 02:12:01 -0300 Subject: [PATCH 09/11] gl_rasterizer: Apply textures and images state --- src/video_core/renderer_opengl/gl_rasterizer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index fd4753af7..70eece9af 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -825,6 +825,8 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) { bind_ubo_pushbuffer.Bind(); bind_ssbo_pushbuffer.Bind(); + state.ApplyTextures(); + state.ApplyImages(); state.ApplyShaderProgram(); state.ApplyProgramPipeline(); From 7228e22098dd97ac89b78484a4f3ee855e37f799 Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 5 Sep 2019 23:25:15 -0300 Subject: [PATCH 10/11] texture_cache: Minor changes --- src/video_core/texture_cache/surface_base.h | 12 +++++------ src/video_core/texture_cache/surface_params.h | 2 -- src/video_core/texture_cache/surface_view.cpp | 2 +- src/video_core/texture_cache/surface_view.h | 20 +++++++++---------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h index bcce8d863..5e497e49f 100644 --- a/src/video_core/texture_cache/surface_base.h +++ b/src/video_core/texture_cache/surface_base.h @@ -195,18 +195,18 @@ public: virtual void DownloadTexture(std::vector& staging_buffer) = 0; - void MarkAsModified(const bool is_modified_, const u64 tick) { + void MarkAsModified(bool is_modified_, u64 tick) { is_modified = is_modified_ || is_target; modification_tick = tick; } - void MarkAsRenderTarget(const bool is_target, const u32 index) { - this->is_target = is_target; - this->index = index; + void MarkAsRenderTarget(bool is_target_, u32 index_) { + is_target = is_target_; + index = index_; } - void MarkAsPicked(const bool is_picked) { - this->is_picked = is_picked; + void MarkAsPicked(bool is_picked_) { + is_picked = is_picked_; } bool IsModified() const { diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index 1011a4d8e..c58e7f8a4 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -4,8 +4,6 @@ #pragma once -#include - #include "common/alignment.h" #include "common/bit_util.h" #include "common/cityhash.h" diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp index 467696a4c..57a1f5803 100644 --- a/src/video_core/texture_cache/surface_view.cpp +++ b/src/video_core/texture_cache/surface_view.cpp @@ -10,7 +10,7 @@ namespace VideoCommon { std::size_t ViewParams::Hash() const { - return static_cast(base_layer) ^ static_cast(num_layers << 16) ^ + return static_cast(base_layer) ^ (static_cast(num_layers) << 16) ^ (static_cast(base_level) << 24) ^ (static_cast(num_levels) << 32) ^ (static_cast(target) << 36); } diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h index 04ca5639b..b17fd11a9 100644 --- a/src/video_core/texture_cache/surface_view.h +++ b/src/video_core/texture_cache/surface_view.h @@ -13,8 +13,8 @@ namespace VideoCommon { struct ViewParams { - ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, u32 num_layers, - u32 base_level, u32 num_levels) + constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, + u32 num_layers, u32 base_level, u32 num_levels) : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, num_levels{num_levels} {} @@ -22,12 +22,6 @@ struct ViewParams { bool operator==(const ViewParams& rhs) const; - VideoCore::Surface::SurfaceTarget target{}; - u32 base_layer{}; - u32 num_layers{}; - u32 base_level{}; - u32 num_levels{}; - bool IsLayered() const { switch (target) { case VideoCore::Surface::SurfaceTarget::Texture1DArray: @@ -39,13 +33,19 @@ struct ViewParams { return false; } } + + VideoCore::Surface::SurfaceTarget target{}; + u32 base_layer{}; + u32 num_layers{}; + u32 base_level{}; + u32 num_levels{}; }; class ViewBase { public: - ViewBase(const ViewParams& params) : params{params} {} + constexpr explicit ViewBase(const ViewParams& params) : params{params} {} - const ViewParams& GetViewParams() const { + constexpr const ViewParams& GetViewParams() const { return params; } From 1f43e5296fcd2debaea672fd9740d2f07223406b Mon Sep 17 00:00:00 2001 From: ReinUsesLisp Date: Thu, 5 Sep 2019 23:26:05 -0300 Subject: [PATCH 11/11] gl_shader_decompiler: Keep track of written images and mark them as modified --- .../renderer_opengl/gl_rasterizer.cpp | 3 ++ .../renderer_opengl/gl_shader_decompiler.cpp | 18 ++++--- .../renderer_opengl/gl_shader_disk_cache.cpp | 15 ++++-- .../renderer_opengl/gl_texture_cache.h | 22 ++++---- src/video_core/shader/decode/image.cpp | 40 +++++++-------- src/video_core/shader/node.h | 50 ++++++++++++------- src/video_core/shader/shader_ir.h | 8 +-- 7 files changed, 93 insertions(+), 63 deletions(-) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 70eece9af..4e266cdad 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -1098,6 +1098,9 @@ void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& t if (!tic.IsBuffer()) { view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source); } + if (entry.IsWritten()) { + view->MarkAsModified(texture_cache.Tick()); + } state.images[binding] = view->GetTexture(); } diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index a5cc1a86f..6edb2ca38 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -389,11 +389,10 @@ public: for (const auto& sampler : ir.GetSamplers()) { entries.samplers.emplace_back(sampler); } - for (const auto& image : ir.GetImages()) { + for (const auto& [offset, image] : ir.GetImages()) { entries.images.emplace_back(image); } - for (const auto& gmem_pair : ir.GetGlobalMemory()) { - const auto& [base, usage] = gmem_pair; + for (const auto& [base, usage] : ir.GetGlobalMemory()) { entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, usage.is_read, usage.is_written); } @@ -706,7 +705,7 @@ private: void DeclareImages() { const auto& images{ir.GetImages()}; - for (const auto& image : images) { + for (const auto& [offset, image] : images) { const std::string image_type = [&]() { switch (image.GetType()) { case Tegra::Shader::ImageType::Texture1D: @@ -726,9 +725,16 @@ private: return "image1D"; } }(); - code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform " + std::string qualifier = "coherent volatile"; + if (image.IsRead() && !image.IsWritten()) { + qualifier += " readonly"; + } else if (image.IsWritten() && !image.IsRead()) { + qualifier += " writeonly"; + } + + code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform " "{} {};", - image.GetIndex(), image_type, GetImage(image)); + image.GetIndex(), qualifier, image_type, GetImage(image)); } if (!images.empty()) { code.AddNewLine(); diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp index 969fe9ced..5450feedf 100644 --- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp @@ -341,13 +341,16 @@ std::optional ShaderDiskCacheOpenGL::LoadDecompiledEn u64 index{}; u32 type{}; u8 is_bindless{}; + u8 is_read{}; + u8 is_written{}; if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || - !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) { + !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) || + !LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) { return {}; } - entry.entries.images.emplace_back( - static_cast(offset), static_cast(index), - static_cast(type), is_bindless != 0); + entry.entries.images.emplace_back(static_cast(offset), static_cast(index), + static_cast(type), + is_bindless != 0, is_written != 0, is_read != 0); } u32 global_memory_count{}; @@ -429,7 +432,9 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std: if (!SaveObjectToPrecompiled(static_cast(image.GetOffset())) || !SaveObjectToPrecompiled(static_cast(image.GetIndex())) || !SaveObjectToPrecompiled(static_cast(image.GetType())) || - !SaveObjectToPrecompiled(static_cast(image.IsBindless() ? 1 : 0))) { + !SaveObjectToPrecompiled(static_cast(image.IsBindless() ? 1 : 0)) || + !SaveObjectToPrecompiled(static_cast(image.IsRead() ? 1 : 0)) || + !SaveObjectToPrecompiled(static_cast(image.IsWritten() ? 1 : 0))) { return false; } } diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h index 21324488a..8e13ab38b 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.h +++ b/src/video_core/renderer_opengl/gl_texture_cache.h @@ -78,6 +78,17 @@ public: /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER void Attach(GLenum attachment, GLenum target) const; + void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, + Tegra::Texture::SwizzleSource y_source, + Tegra::Texture::SwizzleSource z_source, + Tegra::Texture::SwizzleSource w_source); + + void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); + + void MarkAsModified(u64 tick) { + surface.MarkAsModified(true, tick); + } + GLuint GetTexture() const { if (is_proxy) { return surface.GetTexture(); @@ -89,13 +100,6 @@ public: return surface.GetSurfaceParams(); } - void ApplySwizzle(Tegra::Texture::SwizzleSource x_source, - Tegra::Texture::SwizzleSource y_source, - Tegra::Texture::SwizzleSource z_source, - Tegra::Texture::SwizzleSource w_source); - - void DecorateViewName(GPUVAddr gpu_addr, std::string prefix); - private: u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::SwizzleSource y_source, @@ -111,8 +115,8 @@ private: GLenum target{}; OGLTextureView texture_view; - u32 swizzle; - bool is_proxy; + u32 swizzle{}; + bool is_proxy{}; }; class TextureCacheOpenGL final : public TextureCacheBase { diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp index 77151a24b..008109a99 100644 --- a/src/video_core/shader/decode/image.cpp +++ b/src/video_core/shader/decode/image.cpp @@ -61,56 +61,54 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) { } const auto type{instr.sust.image_type}; - const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) - : GetBindlessImage(instr.gpr39, type)}; + auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) + : GetBindlessImage(instr.gpr39, type)}; + image.MarkWrite(); + MetaImage meta{image, values}; const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; bb.push_back(store); break; } default: - UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); + UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName()); } return pc; } -const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { - const auto offset{static_cast(image.index.Value())}; +Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { + const auto offset{static_cast(image.index.Value())}; // If this image has already been used, return the existing mapping. - const auto itr{std::find_if(used_images.begin(), used_images.end(), - [=](const Image& entry) { return entry.GetOffset() == offset; })}; - if (itr != used_images.end()) { - ASSERT(itr->GetType() == type); - return *itr; + const auto it = used_images.find(offset); + if (it != used_images.end()) { + ASSERT(it->second.GetType() == type); + return it->second; } // Otherwise create a new mapping for this image. const std::size_t next_index{used_images.size()}; - const Image entry{offset, next_index, type}; - return *used_images.emplace(entry).first; + return used_images.emplace(offset, Image{offset, next_index, type}).first->second; } -const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, - Tegra::Shader::ImageType type) { +Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) { const Node image_register{GetRegister(reg)}; const auto [base_image, cbuf_index, cbuf_offset]{ TrackCbuf(image_register, global_code, static_cast(global_code.size()))}; const auto cbuf_key{(static_cast(cbuf_index) << 32) | static_cast(cbuf_offset)}; // If this image has already been used, return the existing mapping. - const auto itr{std::find_if(used_images.begin(), used_images.end(), - [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })}; - if (itr != used_images.end()) { - ASSERT(itr->GetType() == type); - return *itr; + const auto it = used_images.find(cbuf_key); + if (it != used_images.end()) { + ASSERT(it->second.GetType() == type); + return it->second; } // Otherwise create a new mapping for this image. const std::size_t next_index{used_images.size()}; - const Image entry{cbuf_index, cbuf_offset, next_index, type}; - return *used_images.emplace(entry).first; + return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type}) + .first->second; } } // namespace VideoCommon::Shader diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h index 0397f4c6e..b29aedce8 100644 --- a/src/video_core/shader/node.h +++ b/src/video_core/shader/node.h @@ -273,50 +273,64 @@ private: bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. }; -class Image { +class Image final { public: - explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) + constexpr explicit Image(u64 offset, std::size_t index, Tegra::Shader::ImageType type) : offset{offset}, index{index}, type{type}, is_bindless{false} {} - explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, - Tegra::Shader::ImageType type) + constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, + Tegra::Shader::ImageType type) : offset{(static_cast(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, is_bindless{true} {} - explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, - bool is_bindless) - : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} + constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, + bool is_bindless, bool is_written, bool is_read) + : offset{offset}, index{index}, type{type}, is_bindless{is_bindless}, + is_written{is_written}, is_read{is_read} {} - std::size_t GetOffset() const { + void MarkRead() { + is_read = true; + } + + void MarkWrite() { + is_written = true; + } + + constexpr std::size_t GetOffset() const { return offset; } - std::size_t GetIndex() const { + constexpr std::size_t GetIndex() const { return index; } - Tegra::Shader::ImageType GetType() const { + constexpr Tegra::Shader::ImageType GetType() const { return type; } - bool IsBindless() const { + constexpr bool IsBindless() const { return is_bindless; } - std::pair GetBindlessCBuf() const { + constexpr bool IsRead() const { + return is_read; + } + + constexpr bool IsWritten() const { + return is_written; + } + + constexpr std::pair GetBindlessCBuf() const { return {static_cast(offset >> 32), static_cast(offset)}; } - bool operator<(const Image& rhs) const { - return std::tie(offset, index, type, is_bindless) < - std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); - } - private: - std::size_t offset{}; + u64 offset{}; std::size_t index{}; Tegra::Shader::ImageType type{}; bool is_bindless{}; + bool is_read{}; + bool is_written{}; }; struct GlobalMemoryBase { diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h index bcc9b79b6..0f891eace 100644 --- a/src/video_core/shader/shader_ir.h +++ b/src/video_core/shader/shader_ir.h @@ -95,7 +95,7 @@ public: return used_samplers; } - const std::set& GetImages() const { + const std::map& GetImages() const { return used_images; } @@ -272,10 +272,10 @@ private: bool is_shadow); /// Accesses an image. - const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); + Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); /// Access a bindless image sampler. - const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); + Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); /// Extracts a sequence of bits from a node Node BitfieldExtract(Node value, u32 offset, u32 bits); @@ -356,7 +356,7 @@ private: std::set used_output_attributes; std::map used_cbufs; std::set used_samplers; - std::set used_images; + std::map used_images; std::array used_clip_distances{}; std::map used_global_memory; bool uses_layer{};