diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core/hle/kernel/server_session.cpp | 9 | ||||
-rw-r--r-- | src/video_core/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 47 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.h | 4 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 18 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_shader_decompiler.cpp | 3 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/utils.cpp | 28 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/utils.h | 20 | ||||
-rw-r--r-- | src/video_core/texture_cache.cpp | 386 | ||||
-rw-r--r-- | src/video_core/texture_cache.h | 586 | ||||
-rw-r--r-- | src/yuzu/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/yuzu/debugger/graphics/graphics_surface.cpp | 516 | ||||
-rw-r--r-- | src/yuzu/debugger/graphics/graphics_surface.h | 96 | ||||
-rw-r--r-- | src/yuzu/main.cpp | 6 | ||||
-rw-r--r-- | src/yuzu/main.h | 2 |
15 files changed, 1060 insertions, 665 deletions
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index a6b2cf06a..696a82cd9 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -28,11 +28,9 @@ ServerSession::~ServerSession() { // the emulated application. // Decrease the port's connection count. - if (parent->port) + if (parent->port) { parent->port->ConnectionClosed(); - - // TODO(Subv): Wake up all the ClientSession's waiting threads and set - // the SendSyncRequest result to 0xC920181A. + } parent->server = nullptr; } @@ -74,9 +72,6 @@ void ServerSession::ClientDisconnected() { handler->ClientDisconnected(this); } - // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set - // their WaitSynchronization result to 0xC920181A. - // Clean up the list of client threads with pending requests, they are unneeded now that the // client endpoint is closed. pending_requesting_threads.clear(); diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 242a0d1cd..5c8ca429e 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -106,6 +106,8 @@ add_library(video_core STATIC textures/decoders.cpp textures/decoders.h textures/texture.h + texture_cache.cpp + texture_cache.h video_core.cpp video_core.h ) diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 7ff1e6737..d250d5cbb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -299,6 +299,10 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { BaseBindings base_bindings; std::array<bool, Maxwell::NumClipDistances> clip_distances{}; + // Prepare packed bindings + bind_ubo_pushbuffer.Setup(base_bindings.cbuf); + bind_ssbo_pushbuffer.Setup(base_bindings.gmem); + for (std::size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) { const auto& shader_config = gpu.regs.shader_config[index]; const Maxwell::ShaderProgram program{static_cast<Maxwell::ShaderProgram>(index)}; @@ -321,8 +325,8 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { &ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment)); // Bind the emulation info buffer - glBindBufferRange(GL_UNIFORM_BUFFER, base_bindings.cbuf, buffer_cache.GetHandle(), offset, - static_cast<GLsizeiptr>(sizeof(ubo))); + bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), offset, + static_cast<GLsizeiptr>(sizeof(ubo))); Shader shader{shader_cache.GetStageProgram(program)}; const auto [program_handle, next_bindings] = @@ -366,6 +370,9 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) { base_bindings = next_bindings; } + bind_ubo_pushbuffer.Bind(); + bind_ssbo_pushbuffer.Bind(); + SyncClipEnabled(clip_distances); gpu.dirty_flags.shaders = false; @@ -900,23 +907,14 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)]; const auto& entries = shader->GetShaderEntries().const_buffers; - constexpr u64 max_binds = Tegra::Engines::Maxwell3D::Regs::MaxConstBuffers; - std::array<GLuint, max_binds> bind_buffers; - std::array<GLintptr, max_binds> bind_offsets; - std::array<GLsizeiptr, max_binds> bind_sizes; - - ASSERT_MSG(entries.size() <= max_binds, "Exceeded expected number of binding points."); - // Upload only the enabled buffers from the 16 constbuffers of each shader stage for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { const auto& used_buffer = entries[bindpoint]; const auto& buffer = shader_stage.const_buffers[used_buffer.GetIndex()]; if (!buffer.enabled) { - // With disabled buffers set values as zero to unbind them - bind_buffers[bindpoint] = 0; - bind_offsets[bindpoint] = 0; - bind_sizes[bindpoint] = 0; + // Set values to zero to unbind buffers + bind_ubo_pushbuffer.Push(0, 0, 0); continue; } @@ -944,30 +942,19 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader const GLintptr const_buffer_offset = buffer_cache.UploadMemory( buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment)); - // Prepare values for multibind - bind_buffers[bindpoint] = buffer_cache.GetHandle(); - bind_offsets[bindpoint] = const_buffer_offset; - bind_sizes[bindpoint] = size; + bind_ubo_pushbuffer.Push(buffer_cache.GetHandle(), const_buffer_offset, size); } - - // The first binding is reserved for emulation values - const GLuint ubo_base_binding = base_bindings.cbuf + 1; - glBindBuffersRange(GL_UNIFORM_BUFFER, ubo_base_binding, static_cast<GLsizei>(entries.size()), - bind_buffers.data(), bind_offsets.data(), bind_sizes.data()); } void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, const Shader& shader, GLenum primitive_mode, BaseBindings base_bindings) { - // TODO(Rodrigo): Use ARB_multi_bind here const auto& entries = shader->GetShaderEntries().global_memory_entries; - - for (u32 bindpoint = 0; bindpoint < static_cast<u32>(entries.size()); ++bindpoint) { - const auto& entry = entries[bindpoint]; - const u32 current_bindpoint = base_bindings.gmem + bindpoint; - const auto& region = global_cache.GetGlobalRegion(entry, stage); - - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, current_bindpoint, region->GetBufferHandle()); + for (std::size_t bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { + const auto& entry{entries[bindpoint]}; + const auto& region{global_cache.GetGlobalRegion(entry, stage)}; + bind_ssbo_pushbuffer.Push(region->GetBufferHandle(), 0, + static_cast<GLsizeiptr>(region->GetSizeInBytes())); } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 54fbf48aa..e4c64ae71 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -28,6 +28,7 @@ #include "video_core/renderer_opengl/gl_shader_cache.h" #include "video_core/renderer_opengl/gl_shader_manager.h" #include "video_core/renderer_opengl/gl_state.h" +#include "video_core/renderer_opengl/utils.h" namespace Core { class System; @@ -229,6 +230,9 @@ private: PrimitiveAssembler primitive_assembler{buffer_cache}; GLint uniform_buffer_alignment; + BindBuffersRangePushBuffer bind_ubo_pushbuffer{GL_UNIFORM_BUFFER}; + BindBuffersRangePushBuffer bind_ssbo_pushbuffer{GL_SHADER_STORAGE_BUFFER}; + std::size_t CalculateVertexArraysSize() const; std::size_t CalculateIndexBufferSize() const; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index aa6da1944..55b6d8591 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -266,6 +266,10 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only, params.component_type = ComponentTypeFromRenderTarget(config.format); params.type = GetFormatType(params.pixel_format); params.width = config.width; + if (!params.is_tiled) { + const u32 bpp = params.GetFormatBpp() / 8; + params.pitch = config.width * bpp; + } params.height = config.height; params.unaligned_height = config.height; params.target = SurfaceTarget::Texture2D; @@ -1175,10 +1179,16 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, return new_surface; } + const bool old_compressed = + GetFormatTuple(old_params.pixel_format, old_params.component_type).compressed; + const bool new_compressed = + GetFormatTuple(new_params.pixel_format, new_params.component_type).compressed; + const bool compatible_formats = + GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format) && + !(old_compressed || new_compressed); // For compatible surfaces, we can just do fast glCopyImageSubData based copy - if (old_params.target == new_params.target && old_params.type == new_params.type && - old_params.depth == new_params.depth && old_params.depth == 1 && - GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format)) { + if (old_params.target == new_params.target && old_params.depth == new_params.depth && + old_params.depth == 1 && compatible_formats) { FastCopySurface(old_surface, new_surface); return new_surface; } @@ -1193,7 +1203,7 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface, case SurfaceTarget::TextureCubemap: case SurfaceTarget::Texture2DArray: case SurfaceTarget::TextureCubeArray: - if (old_params.pixel_format == new_params.pixel_format) + if (compatible_formats) FastLayeredCopySurface(old_surface, new_surface); else { AccurateCopySurface(old_surface, new_surface); diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index 3ea08ef7b..28e490b3c 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -552,8 +552,7 @@ private: } else if (std::holds_alternative<OperationNode>(*offset)) { // Indirect access const std::string final_offset = code.GenerateTemporary(); - code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4) & " + - std::to_string(MAX_CONSTBUFFER_ELEMENTS - 1) + ';'); + code.AddLine("uint " + final_offset + " = (ftou(" + Visit(offset) + ") / 4);"); return fmt::format("{}[{} / 4][{} % 4]", GetConstBuffer(cbuf->GetIndex()), final_offset, final_offset); diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp index d84634cb3..84a987371 100644 --- a/src/video_core/renderer_opengl/utils.cpp +++ b/src/video_core/renderer_opengl/utils.cpp @@ -5,11 +5,39 @@ #include <string> #include <fmt/format.h> #include <glad/glad.h> +#include "common/assert.h" #include "common/common_types.h" #include "video_core/renderer_opengl/utils.h" namespace OpenGL { +BindBuffersRangePushBuffer::BindBuffersRangePushBuffer(GLenum target) : target{target} {} + +BindBuffersRangePushBuffer::~BindBuffersRangePushBuffer() = default; + +void BindBuffersRangePushBuffer::Setup(GLuint first_) { + first = first_; + buffers.clear(); + offsets.clear(); + sizes.clear(); +} + +void BindBuffersRangePushBuffer::Push(GLuint buffer, GLintptr offset, GLsizeiptr size) { + buffers.push_back(buffer); + offsets.push_back(offset); + sizes.push_back(size); +} + +void BindBuffersRangePushBuffer::Bind() const { + const std::size_t count{buffers.size()}; + DEBUG_ASSERT(count == offsets.size() && count == sizes.size()); + if (count == 0) { + return; + } + glBindBuffersRange(target, first, static_cast<GLsizei>(count), buffers.data(), offsets.data(), + sizes.data()); +} + void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) { if (!GLAD_GL_KHR_debug) { return; // We don't need to throw an error as this is just for debugging diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h index 1fcb6fc11..aef45c9dc 100644 --- a/src/video_core/renderer_opengl/utils.h +++ b/src/video_core/renderer_opengl/utils.h @@ -5,11 +5,31 @@ #pragma once #include <string> +#include <vector> #include <glad/glad.h> #include "common/common_types.h" namespace OpenGL { +class BindBuffersRangePushBuffer { +public: + BindBuffersRangePushBuffer(GLenum target); + ~BindBuffersRangePushBuffer(); + + void Setup(GLuint first_); + + void Push(GLuint buffer, GLintptr offset, GLsizeiptr size); + + void Bind() const; + +private: + GLenum target; + GLuint first; + std::vector<GLuint> buffers; + std::vector<GLintptr> offsets; + std::vector<GLsizeiptr> sizes; +}; + void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info = ""); } // namespace OpenGL
\ No newline at end of file diff --git a/src/video_core/texture_cache.cpp b/src/video_core/texture_cache.cpp new file mode 100644 index 000000000..e96eba7cc --- /dev/null +++ b/src/video_core/texture_cache.cpp @@ -0,0 +1,386 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/alignment.h" +#include "common/assert.h" +#include "common/cityhash.h" +#include "common/common_types.h" +#include "core/core.h" +#include "video_core/surface.h" +#include "video_core/texture_cache.h" +#include "video_core/textures/decoders.h" +#include "video_core/textures/texture.h" + +namespace VideoCommon { + +using VideoCore::Surface::SurfaceTarget; + +using VideoCore::Surface::ComponentTypeFromDepthFormat; +using VideoCore::Surface::ComponentTypeFromRenderTarget; +using VideoCore::Surface::ComponentTypeFromTexture; +using VideoCore::Surface::PixelFormatFromDepthFormat; +using VideoCore::Surface::PixelFormatFromRenderTargetFormat; +using VideoCore::Surface::PixelFormatFromTextureFormat; +using VideoCore::Surface::SurfaceTargetFromTextureType; + +constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { + return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); +} + +SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, + const Tegra::Texture::FullTextureInfo& config) { + SurfaceParams params; + params.is_tiled = config.tic.IsTiled(); + params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, + params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, + params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, + params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; + params.pixel_format = + PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false); + params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); + params.type = GetFormatType(params.pixel_format); + params.target = SurfaceTargetFromTextureType(config.tic.texture_type); + params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); + params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); + params.depth = config.tic.Depth(); + if (params.target == SurfaceTarget::TextureCubemap || + params.target == SurfaceTarget::TextureCubeArray) { + params.depth *= 6; + } + params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); + params.unaligned_height = config.tic.Height(); + params.num_levels = config.tic.max_mip_level + 1; + + params.CalculateCachedValues(); + return params; +} + +SurfaceParams SurfaceParams::CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { + SurfaceParams params; + params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.block_width = 1 << std::min(block_width, 5U); + params.block_height = 1 << std::min(block_height, 5U); + params.block_depth = 1 << std::min(block_depth, 5U); + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromDepthFormat(format); + params.component_type = ComponentTypeFromDepthFormat(format); + params.type = GetFormatType(params.pixel_format); + params.width = zeta_width; + params.height = zeta_height; + params.unaligned_height = zeta_height; + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.num_levels = 1; + + params.CalculateCachedValues(); + return params; +} + +SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { + const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; + SurfaceParams params; + params.is_tiled = + config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.block_width = 1 << config.memory_layout.block_width; + params.block_height = 1 << config.memory_layout.block_height; + params.block_depth = 1 << config.memory_layout.block_depth; + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + if (params.is_tiled) { + params.width = config.width; + } else { + const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; + params.pitch = config.width; + params.width = params.pitch / bpp; + } + params.height = config.height; + params.depth = 1; + params.unaligned_height = config.height; + params.target = SurfaceTarget::Texture2D; + params.num_levels = 1; + + params.CalculateCachedValues(); + return params; +} + +SurfaceParams SurfaceParams::CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + SurfaceParams params{}; + params.is_tiled = !config.linear; + params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, + params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, + params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, + params.tile_width_spacing = 1; + params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); + params.component_type = ComponentTypeFromRenderTarget(config.format); + params.type = GetFormatType(params.pixel_format); + params.width = config.width; + params.height = config.height; + params.unaligned_height = config.height; + // TODO(Rodrigo): Try to guess the surface target from depth and layer parameters + params.target = SurfaceTarget::Texture2D; + params.depth = 1; + params.num_levels = 1; + + params.CalculateCachedValues(); + return params; +} + +u32 SurfaceParams::GetMipWidth(u32 level) const { + return std::max(1U, width >> level); +} + +u32 SurfaceParams::GetMipHeight(u32 level) const { + return std::max(1U, height >> level); +} + +u32 SurfaceParams::GetMipDepth(u32 level) const { + return IsLayered() ? depth : std::max(1U, depth >> level); +} + +bool SurfaceParams::IsLayered() const { + switch (target) { + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubeArray: + case SurfaceTarget::TextureCubemap: + return true; + default: + return false; + } +} + +u32 SurfaceParams::GetMipBlockHeight(u32 level) const { + // Auto block resizing algorithm from: + // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c + if (level == 0) { + return block_height; + } + const u32 height{GetMipHeight(level)}; + const u32 default_block_height{GetDefaultBlockHeight(pixel_format)}; + const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height}; + u32 block_height = 16; + while (block_height > 1 && blocks_in_y <= block_height * 4) { + block_height >>= 1; + } + return block_height; +} + +u32 SurfaceParams::GetMipBlockDepth(u32 level) const { + if (level == 0) + return block_depth; + if (target != SurfaceTarget::Texture3D) + return 1; + + const u32 depth{GetMipDepth(level)}; + u32 block_depth = 32; + while (block_depth > 1 && depth * 2 <= block_depth) { + block_depth >>= 1; + } + if (block_depth == 32 && GetMipBlockHeight(level) >= 4) { + return 16; + } + return block_depth; +} + +std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false); + } + return offset; +} + +std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { + std::size_t offset = 0; + for (u32 i = 0; i < level; i++) { + offset += GetInnerMipmapMemorySize(i, true, false, false); + } + return offset; +} + +std::size_t SurfaceParams::GetGuestLayerSize() const { + return GetInnerMemorySize(false, true, false); +} + +std::size_t SurfaceParams::GetHostLayerSize(u32 level) const { + return GetInnerMipmapMemorySize(level, true, IsLayered(), false); +} + +bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const { + if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) != + std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format, + view_params.component_type, view_params.type)) { + return false; + } + + const SurfaceTarget view_target{view_params.target}; + if (view_target == target) { + return true; + } + + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: + return false; + case SurfaceTarget::Texture1DArray: + return view_target == SurfaceTarget::Texture1D; + case SurfaceTarget::Texture2DArray: + return view_target == SurfaceTarget::Texture2D; + case SurfaceTarget::TextureCubemap: + return view_target == SurfaceTarget::Texture2D || + view_target == SurfaceTarget::Texture2DArray; + case SurfaceTarget::TextureCubeArray: + return view_target == SurfaceTarget::Texture2D || + view_target == SurfaceTarget::Texture2DArray || + view_target == SurfaceTarget::TextureCubemap; + default: + UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target)); + return false; + } +} + +bool SurfaceParams::IsPixelFormatZeta() const { + return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && + pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; +} + +void SurfaceParams::CalculateCachedValues() { + guest_size_in_bytes = GetInnerMemorySize(false, false, false); + + // ASTC is uncompressed in software, in emulated as RGBA8 + if (IsPixelFormatASTC(pixel_format)) { + host_size_in_bytes = width * height * depth * 4; + } else { + host_size_in_bytes = GetInnerMemorySize(true, false, false); + } + + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: + num_layers = 1; + break; + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: + num_layers = depth; + break; + default: + UNREACHABLE(); + } +} + +std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, + bool uncompressed) const { + const bool tiled{as_host_size ? false : is_tiled}; + const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; + const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; + const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)}; + const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)}; + const u32 depth{layer_only ? 1U : GetMipDepth(level)}; + return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height, + depth, GetMipBlockHeight(level), GetMipBlockDepth(level)); +} + +std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only, + bool uncompressed) const { + std::size_t size = 0; + for (u32 level = 0; level < num_levels; ++level) { + size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed); + } + if (!as_host_size && is_tiled) { + size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth); + } + return size; +} + +std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const { + std::map<u64, std::pair<u32, u32>> view_offset_map; + switch (target) { + case SurfaceTarget::Texture1D: + case SurfaceTarget::Texture2D: + case SurfaceTarget::Texture3D: { + constexpr u32 layer = 0; + for (u32 level = 0; level < num_levels; ++level) { + const std::size_t offset{GetGuestMipmapLevelOffset(level)}; + view_offset_map.insert({offset, {layer, level}}); + } + break; + } + case SurfaceTarget::Texture1DArray: + case SurfaceTarget::Texture2DArray: + case SurfaceTarget::TextureCubemap: + case SurfaceTarget::TextureCubeArray: { + const std::size_t layer_size{GetGuestLayerSize()}; + for (u32 level = 0; level < num_levels; ++level) { + const std::size_t level_offset{GetGuestMipmapLevelOffset(level)}; + for (u32 layer = 0; layer < num_layers; ++layer) { + const auto layer_offset{static_cast<std::size_t>(layer_size * layer)}; + const std::size_t offset{level_offset + layer_offset}; + view_offset_map.insert({offset, {layer, level}}); + } + } + break; + } + default: + UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target)); + } + return view_offset_map; +} + +bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const { + return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) && + IsInBounds(view_params, layer, level); +} + +bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const { + return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level); +} + +bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const { + if (view_params.target != SurfaceTarget::Texture3D) { + return true; + } + return view_params.depth == GetMipDepth(level); +} + +bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const { + return layer + view_params.num_layers <= num_layers && + level + view_params.num_levels <= num_levels; +} + +std::size_t HasheableSurfaceParams::Hash() const { + return static_cast<std::size_t>( + Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); +} + +bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const { + return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, + height, depth, pitch, unaligned_height, num_levels, pixel_format, + component_type, type, target) == + std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, + rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, + rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type, + rhs.type, rhs.target); +} + +std::size_t ViewKey::Hash() const { + return static_cast<std::size_t>( + Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); +} + +bool ViewKey::operator==(const ViewKey& rhs) const { + return std::tie(base_layer, num_layers, base_level, num_levels) == + std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels); +} + +} // namespace VideoCommon diff --git a/src/video_core/texture_cache.h b/src/video_core/texture_cache.h new file mode 100644 index 000000000..041551691 --- /dev/null +++ b/src/video_core/texture_cache.h @@ -0,0 +1,586 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <list> +#include <memory> +#include <set> +#include <tuple> +#include <type_traits> +#include <unordered_map> + +#include <boost/icl/interval_map.hpp> +#include <boost/range/iterator_range.hpp> + +#include "common/assert.h" +#include "common/common_types.h" +#include "core/memory.h" +#include "video_core/engines/fermi_2d.h" +#include "video_core/engines/maxwell_3d.h" +#include "video_core/gpu.h" +#include "video_core/rasterizer_interface.h" +#include "video_core/surface.h" + +namespace Core { +class System; +} + +namespace Tegra::Texture { +struct FullTextureInfo; +} + +namespace VideoCore { +class RasterizerInterface; +} + +namespace VideoCommon { + +class HasheableSurfaceParams { +public: + std::size_t Hash() const; + + bool operator==(const HasheableSurfaceParams& rhs) const; + +protected: + // Avoid creation outside of a managed environment. + HasheableSurfaceParams() = default; + + bool is_tiled; + u32 block_width; + u32 block_height; + u32 block_depth; + u32 tile_width_spacing; + u32 width; + u32 height; + u32 depth; + u32 pitch; + u32 unaligned_height; + u32 num_levels; + VideoCore::Surface::PixelFormat pixel_format; + VideoCore::Surface::ComponentType component_type; + VideoCore::Surface::SurfaceType type; + VideoCore::Surface::SurfaceTarget target; +}; + +class SurfaceParams final : public HasheableSurfaceParams { +public: + /// Creates SurfaceCachedParams from a texture configuration. + static SurfaceParams CreateForTexture(Core::System& system, + const Tegra::Texture::FullTextureInfo& config); + + /// Creates SurfaceCachedParams for a depth buffer configuration. + static SurfaceParams CreateForDepthBuffer( + Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, + u32 block_width, u32 block_height, u32 block_depth, + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); + + /// Creates SurfaceCachedParams from a framebuffer configuration. + static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); + + /// Creates SurfaceCachedParams from a Fermi2D surface configuration. + static SurfaceParams CreateForFermiCopySurface( + const Tegra::Engines::Fermi2D::Regs::Surface& config); + + bool IsTiled() const { + return is_tiled; + } + + u32 GetBlockWidth() const { + return block_width; + } + + u32 GetTileWidthSpacing() const { + return tile_width_spacing; + } + + u32 GetWidth() const { + return width; + } + + u32 GetHeight() const { + return height; + } + + u32 GetDepth() const { + return depth; + } + + u32 GetPitch() const { + return pitch; + } + + u32 GetNumLevels() const { + return num_levels; + } + + VideoCore::Surface::PixelFormat GetPixelFormat() const { + return pixel_format; + } + + VideoCore::Surface::ComponentType GetComponentType() const { + return component_type; + } + + VideoCore::Surface::SurfaceTarget GetTarget() const { + return target; + } + + VideoCore::Surface::SurfaceType GetType() const { + return type; + } + + std::size_t GetGuestSizeInBytes() const { + return guest_size_in_bytes; + } + + std::size_t GetHostSizeInBytes() const { + return host_size_in_bytes; + } + + u32 GetNumLayers() const { + return num_layers; + } + + /// Returns the width of a given mipmap level. + u32 GetMipWidth(u32 level) const; + + /// Returns the height of a given mipmap level. + u32 GetMipHeight(u32 level) const; + + /// Returns the depth of a given mipmap level. + u32 GetMipDepth(u32 level) const; + + /// Returns true if these parameters are from a layered surface. + bool IsLayered() const; + + /// Returns the block height of a given mipmap level. + u32 GetMipBlockHeight(u32 level) const; + + /// Returns the block depth of a given mipmap level. + u32 GetMipBlockDepth(u32 level) const; + + /// Returns the offset in bytes in guest memory of a given mipmap level. + std::size_t GetGuestMipmapLevelOffset(u32 level) const; + + /// Returns the offset in bytes in host memory (linear) of a given mipmap level. + std::size_t GetHostMipmapLevelOffset(u32 level) const; + + /// Returns the size of a layer in bytes in guest memory. + std::size_t GetGuestLayerSize() const; + + /// Returns the size of a layer in bytes in host memory for a given mipmap level. + std::size_t GetHostLayerSize(u32 level) const; + + /// Returns true if another surface can be familiar with this. This is a loosely defined term + /// that reflects the possibility of these two surface parameters potentially being part of a + /// bigger superset. + bool IsFamiliar(const SurfaceParams& view_params) const; + + /// Returns true if the pixel format is a depth and/or stencil format. + bool IsPixelFormatZeta() const; + + /// Creates a map that redirects an address difference to a layer and mipmap level. + std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const; + + /// Returns true if the passed surface view parameters is equal or a valid subset of this. + bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const; + +private: + /// Calculates values that can be deduced from HasheableSurfaceParams. + void CalculateCachedValues(); + + /// Returns the size of a given mipmap level. + std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, + bool uncompressed) const; + + /// Returns the size of all mipmap levels and aligns as needed. + std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const; + + /// Returns true if the passed view width and height match the size of this params in a given + /// mipmap level. + bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const; + + /// Returns true if the passed view depth match the size of this params in a given mipmap level. + bool IsDepthValid(const SurfaceParams& view_params, u32 level) const; + + /// Returns true if the passed view layers and mipmap levels are in bounds. + bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const; + + std::size_t guest_size_in_bytes; + std::size_t host_size_in_bytes; + u32 num_layers; +}; + +struct ViewKey { + std::size_t Hash() const; + + bool operator==(const ViewKey& rhs) const; + + u32 base_layer{}; + u32 num_layers{}; + u32 base_level{}; + u32 num_levels{}; +}; + +} // namespace VideoCommon + +namespace std { + +template <> +struct hash<VideoCommon::SurfaceParams> { + std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { + return k.Hash(); + } +}; + +template <> +struct hash<VideoCommon::ViewKey> { + std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept { + return k.Hash(); + } +}; + +} // namespace std + +namespace VideoCommon { + +template <typename TView, typename TExecutionContext> +class SurfaceBase { + static_assert(std::is_trivially_copyable_v<TExecutionContext>); + +public: + virtual void LoadBuffer() = 0; + + virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0; + + virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0; + + TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) { + if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) { + // It can't be a view if it's in a prior address. + return {}; + } + + const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)}; + const auto it{view_offset_map.find(relative_offset)}; + if (it == view_offset_map.end()) { + // Couldn't find an aligned view. + return {}; + } + const auto [layer, level] = it->second; + + if (!params.IsViewValid(view_params, layer, level)) { + return {}; + } + + return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels()); + } + + VAddr GetCpuAddr() const { + ASSERT(is_registered); + return cpu_addr; + } + + u8* GetHostPtr() const { + ASSERT(is_registered); + return host_ptr; + } + + CacheAddr GetCacheAddr() const { + ASSERT(is_registered); + return cache_addr; + } + + std::size_t GetSizeInBytes() const { + return params.GetGuestSizeInBytes(); + } + + void MarkAsModified(bool is_modified_) { + is_modified = is_modified_; + } + + const SurfaceParams& GetSurfaceParams() const { + return params; + } + + TView* GetView(VAddr view_addr, const SurfaceParams& view_params) { + TView* view{TryGetView(view_addr, view_params)}; + ASSERT(view != nullptr); + return view; + } + + void Register(VAddr cpu_addr_, u8* host_ptr_) { + ASSERT(!is_registered); + is_registered = true; + cpu_addr = cpu_addr_; + host_ptr = host_ptr_; + cache_addr = ToCacheAddr(host_ptr_); + } + + void Register(VAddr cpu_addr_) { + Register(cpu_addr_, Memory::GetPointer(cpu_addr_)); + } + + void Unregister() { + ASSERT(is_registered); + is_registered = false; + } + + bool IsRegistered() const { + return is_registered; + } + +protected: + explicit SurfaceBase(const SurfaceParams& params) + : params{params}, view_offset_map{params.CreateViewOffsetMap()} {} + + ~SurfaceBase() = default; + + virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0; + + bool IsModified() const { + return is_modified; + } + + const SurfaceParams params; + +private: + TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) { + const ViewKey key{base_layer, num_layers, base_level, num_levels}; + const auto [entry, is_cache_miss] = views.try_emplace(key); + auto& view{entry->second}; + if (is_cache_miss) { + view = CreateView(key); + } + return view.get(); + } + + const std::map<u64, std::pair<u32, u32>> view_offset_map; + + VAddr cpu_addr{}; + u8* host_ptr{}; + CacheAddr cache_addr{}; + bool is_modified{}; + bool is_registered{}; + std::unordered_map<ViewKey, std::unique_ptr<TView>> views; +}; + +template <typename TSurface, typename TView, typename TExecutionContext> +class TextureCache { + static_assert(std::is_trivially_copyable_v<TExecutionContext>); + using ResultType = std::tuple<TView*, TExecutionContext>; + using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>; + using IntervalType = typename IntervalMap::interval_type; + +public: + void InvalidateRegion(CacheAddr addr, std::size_t size) { + for (TSurface* surface : GetSurfacesInRegion(addr, size)) { + if (!surface->IsRegistered()) { + // Skip duplicates + continue; + } + Unregister(surface); + } + } + + ResultType GetTextureSurface(TExecutionContext exctx, + const Tegra::Texture::FullTextureInfo& config) { + auto& memory_manager{system.GPU().MemoryManager()}; + const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())}; + if (!cpu_addr) { + return {{}, exctx}; + } + const auto params{SurfaceParams::CreateForTexture(system, config)}; + return GetSurfaceView(exctx, *cpu_addr, params, true); + } + + ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) { + const auto& regs{system.GPU().Maxwell3D().regs}; + if (!regs.zeta.Address() || !regs.zeta_enable) { + return {{}, exctx}; + } + + auto& memory_manager{system.GPU().MemoryManager()}; + const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())}; + if (!cpu_addr) { + return {{}, exctx}; + } + + const auto depth_params{SurfaceParams::CreateForDepthBuffer( + system, regs.zeta_width, regs.zeta_height, regs.zeta.format, + regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, + regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; + return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents); + } + + ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index, + bool preserve_contents) { + ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); + + const auto& regs{system.GPU().Maxwell3D().regs}; + if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || + regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { + return {{}, exctx}; + } + + auto& memory_manager{system.GPU().MemoryManager()}; + const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; + const auto cpu_addr{memory_manager.GpuToCpuAddress( + config.Address() + config.base_layer * config.layer_stride * sizeof(u32))}; + if (!cpu_addr) { + return {{}, exctx}; + } + + return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), + preserve_contents); + } + + ResultType GetFermiSurface(TExecutionContext exctx, + const Tegra::Engines::Fermi2D::Regs::Surface& config) { + const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())}; + ASSERT(cpu_addr); + return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config), + true); + } + + TSurface* TryFindFramebufferSurface(const u8* host_ptr) const { + const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))}; + return it != registered_surfaces.end() ? *it->second.begin() : nullptr; + } + +protected: + TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) + : system{system}, rasterizer{rasterizer} {} + + ~TextureCache() = default; + + virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, + const SurfaceParams& params, bool preserve_contents, + const std::vector<TSurface*>& overlaps) = 0; + + virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0; + + void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) { + surface->Register(cpu_addr, host_ptr); + registered_surfaces.add({GetSurfaceInterval(surface), {surface}}); + rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1); + } + + void Unregister(TSurface* surface) { + registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}}); + rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1); + surface->Unregister(); + } + + TSurface* GetUncachedSurface(const SurfaceParams& params) { + if (TSurface* surface = TryGetReservedSurface(params); surface) + return surface; + // No reserved surface available, create a new one and reserve it + auto new_surface{CreateSurface(params)}; + TSurface* surface{new_surface.get()}; + ReserveSurface(params, std::move(new_surface)); + return surface; + } + + Core::System& system; + +private: + ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params, + bool preserve_contents) { + const auto host_ptr{Memory::GetPointer(cpu_addr)}; + const auto cache_addr{ToCacheAddr(host_ptr)}; + const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())}; + if (overlaps.empty()) { + return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); + } + + if (overlaps.size() == 1) { + if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view) + return {view, exctx}; + } + + TView* fast_view; + std::tie(fast_view, exctx) = + TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps); + + for (TSurface* surface : overlaps) { + if (!fast_view) { + // Flush even when we don't care about the contents, to preserve memory not written + // by the new surface. + exctx = surface->FlushBuffer(exctx); + } + Unregister(surface); + } + + if (fast_view) { + return {fast_view, exctx}; + } + + return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); + } + + ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, + const SurfaceParams& params, bool preserve_contents) { + TSurface* new_surface{GetUncachedSurface(params)}; + Register(new_surface, cpu_addr, host_ptr); + if (preserve_contents) { + exctx = LoadSurface(exctx, new_surface); + } + return {new_surface->GetView(cpu_addr, params), exctx}; + } + + TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) { + surface->LoadBuffer(); + exctx = surface->UploadTexture(exctx); + surface->MarkAsModified(false); + return exctx; + } + + std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const { + if (size == 0) { + return {}; + } + const IntervalType interval{cache_addr, cache_addr + size}; + + std::vector<TSurface*> surfaces; + for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) { + surfaces.push_back(*pair.second.begin()); + } + return surfaces; + } + + void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) { + surface_reserve[params].push_back(std::move(surface)); + } + + TSurface* TryGetReservedSurface(const SurfaceParams& params) { + auto search{surface_reserve.find(params)}; + if (search == surface_reserve.end()) { + return {}; + } + for (auto& surface : search->second) { + if (!surface->IsRegistered()) { + return surface.get(); + } + } + return {}; + } + + IntervalType GetSurfaceInterval(TSurface* surface) const { + return IntervalType::right_open(surface->GetCacheAddr(), + surface->GetCacheAddr() + surface->GetSizeInBytes()); + } + + VideoCore::RasterizerInterface& rasterizer; + + IntervalMap registered_surfaces; + + /// The surface reserve is a "backup" cache, this is where we put unique surfaces that have + /// previously been used. This is to prevent surfaces from being constantly created and + /// destroyed when used with different surface parameters. + std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve; +}; + +} // namespace VideoCommon diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 732a1bf89..2eb86d6e5 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -56,8 +56,6 @@ add_executable(yuzu debugger/graphics/graphics_breakpoints.cpp debugger/graphics/graphics_breakpoints.h debugger/graphics/graphics_breakpoints_p.h - debugger/graphics/graphics_surface.cpp - debugger/graphics/graphics_surface.h debugger/console.cpp debugger/console.h debugger/profiler.cpp diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp deleted file mode 100644 index f2d14becf..000000000 --- a/src/yuzu/debugger/graphics/graphics_surface.cpp +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <QBoxLayout> -#include <QComboBox> -#include <QDebug> -#include <QFileDialog> -#include <QLabel> -#include <QMessageBox> -#include <QMouseEvent> -#include <QPushButton> -#include <QScrollArea> -#include <QSpinBox> -#include "common/vector_math.h" -#include "core/core.h" -#include "core/memory.h" -#include "video_core/engines/maxwell_3d.h" -#include "video_core/gpu.h" -#include "video_core/textures/decoders.h" -#include "video_core/textures/texture.h" -#include "yuzu/debugger/graphics/graphics_surface.h" -#include "yuzu/util/spinbox.h" - -static Tegra::Texture::TextureFormat ConvertToTextureFormat( - Tegra::RenderTargetFormat render_target_format) { - switch (render_target_format) { - case Tegra::RenderTargetFormat::RGBA8_UNORM: - return Tegra::Texture::TextureFormat::A8R8G8B8; - case Tegra::RenderTargetFormat::RGB10_A2_UNORM: - return Tegra::Texture::TextureFormat::A2B10G10R10; - default: - UNIMPLEMENTED_MSG("Unimplemented RT format"); - return Tegra::Texture::TextureFormat::A8R8G8B8; - } -} - -SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) - : QLabel(parent), surface_widget(surface_widget_) {} - -SurfacePicture::~SurfacePicture() = default; - -void SurfacePicture::mousePressEvent(QMouseEvent* event) { - // Only do something while the left mouse button is held down - if (!(event->buttons() & Qt::LeftButton)) - return; - - if (pixmap() == nullptr) - return; - - if (surface_widget) - surface_widget->Pick(event->x() * pixmap()->width() / width(), - event->y() * pixmap()->height() / height()); -} - -void SurfacePicture::mouseMoveEvent(QMouseEvent* event) { - // We also want to handle the event if the user moves the mouse while holding down the LMB - mousePressEvent(event); -} - -GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, - QWidget* parent) - : BreakPointObserverDock(debug_context, tr("Maxwell Surface Viewer"), parent), - surface_source(Source::RenderTarget0) { - setObjectName("MaxwellSurface"); - - surface_source_list = new QComboBox; - surface_source_list->addItem(tr("Render Target 0")); - surface_source_list->addItem(tr("Render Target 1")); - surface_source_list->addItem(tr("Render Target 2")); - surface_source_list->addItem(tr("Render Target 3")); - surface_source_list->addItem(tr("Render Target 4")); - surface_source_list->addItem(tr("Render Target 5")); - surface_source_list->addItem(tr("Render Target 6")); - surface_source_list->addItem(tr("Render Target 7")); - surface_source_list->addItem(tr("Z Buffer")); - surface_source_list->addItem(tr("Custom")); - surface_source_list->setCurrentIndex(static_cast<int>(surface_source)); - - surface_address_control = new CSpinBox; - surface_address_control->SetBase(16); - surface_address_control->SetRange(0, 0x7FFFFFFFFFFFFFFF); - surface_address_control->SetPrefix("0x"); - - unsigned max_dimension = 16384; // TODO: Find actual maximum - - surface_width_control = new QSpinBox; - surface_width_control->setRange(0, max_dimension); - - surface_height_control = new QSpinBox; - surface_height_control->setRange(0, max_dimension); - - surface_picker_x_control = new QSpinBox; - surface_picker_x_control->setRange(0, max_dimension - 1); - - surface_picker_y_control = new QSpinBox; - surface_picker_y_control->setRange(0, max_dimension - 1); - - // clang-format off - // Color formats sorted by Maxwell texture format index - const QStringList surface_formats{ - tr("None"), - QStringLiteral("R32_G32_B32_A32"), - QStringLiteral("R32_G32_B32"), - QStringLiteral("R16_G16_B16_A16"), - QStringLiteral("R32_G32"), - QStringLiteral("R32_B24G8"), - QStringLiteral("ETC2_RGB"), - QStringLiteral("X8B8G8R8"), - QStringLiteral("A8R8G8B8"), - QStringLiteral("A2B10G10R10"), - QStringLiteral("ETC2_RGB_PTA"), - QStringLiteral("ETC2_RGBA"), - QStringLiteral("R16_G16"), - QStringLiteral("G8R24"), - QStringLiteral("G24R8"), - QStringLiteral("R32"), - QStringLiteral("BC6H_SF16"), - QStringLiteral("BC6H_UF16"), - QStringLiteral("A4B4G4R4"), - QStringLiteral("A5B5G5R1"), - QStringLiteral("A1B5G5R5"), - QStringLiteral("B5G6R5"), - QStringLiteral("B6G5R5"), - QStringLiteral("BC7U"), - QStringLiteral("G8R8"), - QStringLiteral("EAC"), - QStringLiteral("EACX2"), - QStringLiteral("R16"), - QStringLiteral("Y8_VIDEO"), - QStringLiteral("R8"), - QStringLiteral("G4R4"), - QStringLiteral("R1"), - QStringLiteral("E5B9G9R9_SHAREDEXP"), - QStringLiteral("BF10GF11RF11"), - QStringLiteral("G8B8G8R8"), - QStringLiteral("B8G8R8G8"), - QStringLiteral("DXT1"), - QStringLiteral("DXT23"), - QStringLiteral("DXT45"), - QStringLiteral("DXN1"), - QStringLiteral("DXN2"), - QStringLiteral("Z24S8"), - QStringLiteral("X8Z24"), - QStringLiteral("S8Z24"), - QStringLiteral("X4V4Z24__COV4R4V"), - QStringLiteral("X4V4Z24__COV8R8V"), - QStringLiteral("V8Z24__COV4R12V"), - QStringLiteral("ZF32"), - QStringLiteral("ZF32_X24S8"), - QStringLiteral("X8Z24_X20V4S8__COV4R4V"), - QStringLiteral("X8Z24_X20V4S8__COV8R8V"), - QStringLiteral("ZF32_X20V4X8__COV4R4V"), - QStringLiteral("ZF32_X20V4X8__COV8R8V"), - QStringLiteral("ZF32_X20V4S8__COV4R4V"), - QStringLiteral("ZF32_X20V4S8__COV8R8V"), - QStringLiteral("X8Z24_X16V8S8__COV4R12V"), - QStringLiteral("ZF32_X16V8X8__COV4R12V"), - QStringLiteral("ZF32_X16V8S8__COV4R12V"), - QStringLiteral("Z16"), - QStringLiteral("V8Z24__COV8R24V"), - QStringLiteral("X8Z24_X16V8S8__COV8R24V"), - QStringLiteral("ZF32_X16V8X8__COV8R24V"), - QStringLiteral("ZF32_X16V8S8__COV8R24V"), - QStringLiteral("ASTC_2D_4X4"), - QStringLiteral("ASTC_2D_5X5"), - QStringLiteral("ASTC_2D_6X6"), - QStringLiteral("ASTC_2D_8X8"), - QStringLiteral("ASTC_2D_10X10"), - QStringLiteral("ASTC_2D_12X12"), - QStringLiteral("ASTC_2D_5X4"), - QStringLiteral("ASTC_2D_6X5"), - QStringLiteral("ASTC_2D_8X6"), - QStringLiteral("ASTC_2D_10X8"), - QStringLiteral("ASTC_2D_12X10"), - QStringLiteral("ASTC_2D_8X5"), - QStringLiteral("ASTC_2D_10X5"), - QStringLiteral("ASTC_2D_10X6"), - }; - // clang-format on - - surface_format_control = new QComboBox; - surface_format_control->addItems(surface_formats); - - surface_info_label = new QLabel(); - surface_info_label->setWordWrap(true); - - surface_picture_label = new SurfacePicture(0, this); - surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop); - surface_picture_label->setScaledContents(false); - - auto scroll_area = new QScrollArea(); - scroll_area->setBackgroundRole(QPalette::Dark); - scroll_area->setWidgetResizable(false); - scroll_area->setWidget(surface_picture_label); - - save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save")); - - // Connections - connect(this, &GraphicsSurfaceWidget::Update, this, &GraphicsSurfaceWidget::OnUpdate); - connect(surface_source_list, qOverload<int>(&QComboBox::currentIndexChanged), this, - &GraphicsSurfaceWidget::OnSurfaceSourceChanged); - connect(surface_address_control, &CSpinBox::ValueChanged, this, - &GraphicsSurfaceWidget::OnSurfaceAddressChanged); - connect(surface_width_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfaceWidthChanged); - connect(surface_height_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfaceHeightChanged); - connect(surface_format_control, qOverload<int>(&QComboBox::currentIndexChanged), this, - &GraphicsSurfaceWidget::OnSurfaceFormatChanged); - connect(surface_picker_x_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfacePickerXChanged); - connect(surface_picker_y_control, qOverload<int>(&QSpinBox::valueChanged), this, - &GraphicsSurfaceWidget::OnSurfacePickerYChanged); - connect(save_surface, &QPushButton::clicked, this, &GraphicsSurfaceWidget::SaveSurface); - - auto main_widget = new QWidget; - auto main_layout = new QVBoxLayout; - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Source:"))); - sub_layout->addWidget(surface_source_list); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("GPU Address:"))); - sub_layout->addWidget(surface_address_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Width:"))); - sub_layout->addWidget(surface_width_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Height:"))); - sub_layout->addWidget(surface_height_control); - main_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Format:"))); - sub_layout->addWidget(surface_format_control); - main_layout->addLayout(sub_layout); - } - main_layout->addWidget(scroll_area); - - auto info_layout = new QHBoxLayout; - { - auto xy_layout = new QVBoxLayout; - { - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("X:"))); - sub_layout->addWidget(surface_picker_x_control); - xy_layout->addLayout(sub_layout); - } - { - auto sub_layout = new QHBoxLayout; - sub_layout->addWidget(new QLabel(tr("Y:"))); - sub_layout->addWidget(surface_picker_y_control); - xy_layout->addLayout(sub_layout); - } - } - info_layout->addLayout(xy_layout); - surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); - info_layout->addWidget(surface_info_label); - } - main_layout->addLayout(info_layout); - - main_layout->addWidget(save_surface); - main_widget->setLayout(main_layout); - setWidget(main_widget); - - // Load current data - TODO: Make sure this works when emulation is not running - if (debug_context && debug_context->at_breakpoint) { - emit Update(); - widget()->setEnabled(debug_context->at_breakpoint); - } else { - widget()->setEnabled(false); - } -} - -void GraphicsSurfaceWidget::OnBreakPointHit(Tegra::DebugContext::Event event, void* data) { - emit Update(); - widget()->setEnabled(true); -} - -void GraphicsSurfaceWidget::OnResumed() { - widget()->setEnabled(false); -} - -void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) { - surface_source = static_cast<Source>(new_value); - emit Update(); -} - -void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) { - if (surface_address != new_value) { - surface_address = static_cast<GPUVAddr>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value) { - if (surface_width != static_cast<unsigned>(new_value)) { - surface_width = static_cast<unsigned>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value) { - if (surface_height != static_cast<unsigned>(new_value)) { - surface_height = static_cast<unsigned>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value) { - if (surface_format != static_cast<Tegra::Texture::TextureFormat>(new_value)) { - surface_format = static_cast<Tegra::Texture::TextureFormat>(new_value); - - surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); - emit Update(); - } -} - -void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value) { - if (surface_picker_x != new_value) { - surface_picker_x = new_value; - Pick(surface_picker_x, surface_picker_y); - } -} - -void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value) { - if (surface_picker_y != new_value) { - surface_picker_y = new_value; - Pick(surface_picker_x, surface_picker_y); - } -} - -void GraphicsSurfaceWidget::Pick(int x, int y) { - surface_picker_x_control->setValue(x); - surface_picker_y_control->setValue(y); - - if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || - y >= static_cast<int>(surface_height)) { - surface_info_label->setText(tr("Pixel out of bounds")); - surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); - return; - } - - surface_info_label->setText(QString("Raw: <Unimplemented>\n(%1)").arg("<Unimplemented>")); - surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); -} - -void GraphicsSurfaceWidget::OnUpdate() { - auto& gpu = Core::System::GetInstance().GPU(); - - QPixmap pixmap; - - switch (surface_source) { - case Source::RenderTarget0: - case Source::RenderTarget1: - case Source::RenderTarget2: - case Source::RenderTarget3: - case Source::RenderTarget4: - case Source::RenderTarget5: - case Source::RenderTarget6: - case Source::RenderTarget7: { - // TODO: Store a reference to the registers in the debug context instead of accessing them - // directly... - - const auto& registers = gpu.Maxwell3D().regs; - const auto& rt = registers.rt[static_cast<std::size_t>(surface_source) - - static_cast<std::size_t>(Source::RenderTarget0)]; - - surface_address = rt.Address(); - surface_width = rt.width; - surface_height = rt.height; - if (rt.format != Tegra::RenderTargetFormat::NONE) { - surface_format = ConvertToTextureFormat(rt.format); - } - - break; - } - - case Source::Custom: { - // Keep user-specified values - break; - } - - default: - qDebug() << "Unknown surface source " << static_cast<int>(surface_source); - break; - } - - surface_address_control->SetValue(surface_address); - surface_width_control->setValue(surface_width); - surface_height_control->setValue(surface_height); - surface_format_control->setCurrentIndex(static_cast<int>(surface_format)); - - if (surface_address == 0) { - surface_picture_label->hide(); - surface_info_label->setText(tr("(invalid surface address)")); - surface_info_label->setAlignment(Qt::AlignCenter); - surface_picker_x_control->setEnabled(false); - surface_picker_y_control->setEnabled(false); - save_surface->setEnabled(false); - return; - } - - // TODO: Implement a good way to visualize alpha components! - - QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32); - - // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles. - // Needs to be fixed if we plan to use this feature more, otherwise we may remove it. - auto unswizzled_data = Tegra::Texture::UnswizzleTexture( - gpu.MemoryManager().GetPointer(surface_address), 1, 1, - Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U); - - auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format, - surface_width, surface_height); - - surface_picture_label->show(); - - for (unsigned int y = 0; y < surface_height; ++y) { - for (unsigned int x = 0; x < surface_width; ++x) { - Common::Vec4<u8> color; - color[0] = texture_data[x + y * surface_width + 0]; - color[1] = texture_data[x + y * surface_width + 1]; - color[2] = texture_data[x + y * surface_width + 2]; - color[3] = texture_data[x + y * surface_width + 3]; - decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); - } - } - - pixmap = QPixmap::fromImage(decoded_image); - surface_picture_label->setPixmap(pixmap); - surface_picture_label->resize(pixmap.size()); - - // Update the info with pixel data - surface_picker_x_control->setEnabled(true); - surface_picker_y_control->setEnabled(true); - Pick(surface_picker_x, surface_picker_y); - - // Enable saving the converted pixmap to file - save_surface->setEnabled(true); -} - -void GraphicsSurfaceWidget::SaveSurface() { - const QString png_filter = tr("Portable Network Graphic (*.png)"); - const QString bin_filter = tr("Binary data (*.bin)"); - - QString selected_filter; - const QString filename = QFileDialog::getSaveFileName( - this, tr("Save Surface"), - QStringLiteral("texture-0x%1.png").arg(QString::number(surface_address, 16)), - QStringLiteral("%1;;%2").arg(png_filter, bin_filter), &selected_filter); - - if (filename.isEmpty()) { - // If the user canceled the dialog, don't save anything. - return; - } - - if (selected_filter == png_filter) { - const QPixmap* const pixmap = surface_picture_label->pixmap(); - ASSERT_MSG(pixmap != nullptr, "No pixmap set"); - - QFile file{filename}; - if (!file.open(QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename)); - return; - } - - if (!pixmap->save(&file, "PNG")) { - QMessageBox::warning(this, tr("Error"), - tr("Failed to save surface data to file '%1'").arg(filename)); - } - } else if (selected_filter == bin_filter) { - auto& gpu = Core::System::GetInstance().GPU(); - const std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address); - - const u8* const buffer = Memory::GetPointer(*address); - ASSERT_MSG(buffer != nullptr, "Memory not accessible"); - - QFile file{filename}; - if (!file.open(QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Error"), tr("Failed to open file '%1'").arg(filename)); - return; - } - - const int size = - surface_width * surface_height * Tegra::Texture::BytesPerPixel(surface_format); - const QByteArray data(reinterpret_cast<const char*>(buffer), size); - if (file.write(data) != data.size()) { - QMessageBox::warning( - this, tr("Error"), - tr("Failed to completely write surface data to file. The saved data will " - "likely be corrupt.")); - } - } else { - UNREACHABLE_MSG("Unhandled filter selected"); - } -} diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h deleted file mode 100644 index 89445b18f..000000000 --- a/src/yuzu/debugger/graphics/graphics_surface.h +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <QLabel> -#include <QPushButton> -#include "video_core/memory_manager.h" -#include "video_core/textures/texture.h" -#include "yuzu/debugger/graphics/graphics_breakpoint_observer.h" - -class QComboBox; -class QSpinBox; -class CSpinBox; - -class GraphicsSurfaceWidget; - -class SurfacePicture : public QLabel { - Q_OBJECT - -public: - explicit SurfacePicture(QWidget* parent = nullptr, - GraphicsSurfaceWidget* surface_widget = nullptr); - ~SurfacePicture() override; - -protected slots: - void mouseMoveEvent(QMouseEvent* event) override; - void mousePressEvent(QMouseEvent* event) override; - -private: - GraphicsSurfaceWidget* surface_widget; -}; - -class GraphicsSurfaceWidget : public BreakPointObserverDock { - Q_OBJECT - - using Event = Tegra::DebugContext::Event; - - enum class Source { - RenderTarget0 = 0, - RenderTarget1 = 1, - RenderTarget2 = 2, - RenderTarget3 = 3, - RenderTarget4 = 4, - RenderTarget5 = 5, - RenderTarget6 = 6, - RenderTarget7 = 7, - ZBuffer = 8, - Custom = 9, - }; - -public: - explicit GraphicsSurfaceWidget(std::shared_ptr<Tegra::DebugContext> debug_context, - QWidget* parent = nullptr); - void Pick(int x, int y); - -public slots: - void OnSurfaceSourceChanged(int new_value); - void OnSurfaceAddressChanged(qint64 new_value); - void OnSurfaceWidthChanged(int new_value); - void OnSurfaceHeightChanged(int new_value); - void OnSurfaceFormatChanged(int new_value); - void OnSurfacePickerXChanged(int new_value); - void OnSurfacePickerYChanged(int new_value); - void OnUpdate(); - -signals: - void Update(); - -private: - void OnBreakPointHit(Tegra::DebugContext::Event event, void* data) override; - void OnResumed() override; - - void SaveSurface(); - - QComboBox* surface_source_list; - CSpinBox* surface_address_control; - QSpinBox* surface_width_control; - QSpinBox* surface_height_control; - QComboBox* surface_format_control; - - SurfacePicture* surface_picture_label; - QSpinBox* surface_picker_x_control; - QSpinBox* surface_picker_y_control; - QLabel* surface_info_label; - QPushButton* save_surface; - - Source surface_source; - GPUVAddr surface_address; - unsigned surface_width; - unsigned surface_height; - Tegra::Texture::TextureFormat surface_format; - int surface_picker_x = 0; - int surface_picker_y = 0; -}; diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index d5a328d92..ca231d710 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -90,7 +90,6 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "yuzu/configuration/configure_dialog.h" #include "yuzu/debugger/console.h" #include "yuzu/debugger/graphics/graphics_breakpoints.h" -#include "yuzu/debugger/graphics/graphics_surface.h" #include "yuzu/debugger/profiler.h" #include "yuzu/debugger/wait_tree.h" #include "yuzu/discord.h" @@ -483,11 +482,6 @@ void GMainWindow::InitializeDebugWidgets() { graphicsBreakpointsWidget->hide(); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); - graphicsSurfaceWidget = new GraphicsSurfaceWidget(debug_context, this); - addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceWidget); - graphicsSurfaceWidget->hide(); - debug_menu->addAction(graphicsSurfaceWidget->toggleViewAction()); - waitTreeWidget = new WaitTreeWidget(this); addDockWidget(Qt::LeftDockWidgetArea, waitTreeWidget); waitTreeWidget->hide(); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index c727e942c..85e3810f2 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -23,7 +23,6 @@ class EmuThread; class GameList; class GImageInfo; class GraphicsBreakPointsWidget; -class GraphicsSurfaceWidget; class GRenderWindow; class LoadingScreen; class MicroProfileDialog; @@ -240,7 +239,6 @@ private: ProfilerWidget* profilerWidget; MicroProfileDialog* microProfileDialog; GraphicsBreakPointsWidget* graphicsBreakpointsWidget; - GraphicsSurfaceWidget* graphicsSurfaceWidget; WaitTreeWidget* waitTreeWidget; QAction* actions_recent_files[max_recent_files_item]; |