Compare commits

..

11 Commits

Author SHA1 Message Date
ReinUsesLisp
93af663683 gl_shader_manager: Move code to source file and minor clean up 2019-04-10 19:29:15 -03:00
bunnei
97648f4841 Merge pull request #2345 from ReinUsesLisp/multibind
gl_rasterizer: Use ARB_multi_bind to update buffers with a single call per drawcall
2019-04-10 11:23:19 -04:00
bunnei
1312cf15d6 Merge pull request #2377 from lioncash/todo
kernel/server_session: Remove obsolete TODOs
2019-04-10 10:29:24 -04:00
Lioncash
08d507a196 kernel/server_session: Remove obsolete TODOs
These are holdovers from Citra.
2019-04-09 23:34:49 -04:00
bunnei
ed9dba89d3 Merge pull request #2375 from FernandoS27/fix-ldc
Remove unnecessary bounding in LD_C
2019-04-09 21:23:24 -04:00
bunnei
f46c3164e7 Merge pull request #2353 from lioncash/surface
yuzu/debugger: Remove graphics surface viewer
2019-04-09 21:20:02 -04:00
Fernando Sahmkow
c9f35d96be Remove bounding in LD_C 2019-04-09 20:02:11 -04:00
Lioncash
218ae888f3 yuzu/debugger: Remove graphics surface viewer
This doesn't actually work anymore, and given how long it's been left in
that state, it's unlikely anyone actually seriously used it.

Generally it's preferable to use RenderDoc or Nsight to view surfaces.
2019-04-05 23:54:00 -04:00
ReinUsesLisp
34c3e2c786 renderer_opengl/utils: Skip empty binds 2019-04-05 19:19:49 -03:00
ReinUsesLisp
b631c09e72 gl_rasterizer: Use ARB_multi_bind to update SSBOs 2019-04-05 19:18:43 -03:00
ReinUsesLisp
2d1f054c61 gl_rasterizer: Use ARB_multi_bind to update UBOs across stages 2019-04-05 19:10:46 -03:00
14 changed files with 200 additions and 720 deletions

View File

@@ -21,6 +21,11 @@
#if defined(_MSC_VER)
#include <cstdlib>
#elif defined(__linux__)
#include <byteswap.h>
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || \
defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/endian.h>
#endif
#include <cstring>
#include "common/common_types.h"
@@ -57,49 +62,86 @@
namespace Common {
#ifdef _MSC_VER
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return _byteswap_ushort(data);
inline u16 swap16(u16 _data) {
return _byteswap_ushort(_data);
}
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return _byteswap_ulong(data);
inline u32 swap32(u32 _data) {
return _byteswap_ulong(_data);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return _byteswap_uint64(data);
inline u64 swap64(u64 _data) {
return _byteswap_uint64(_data);
}
#elif defined(__clang__) || defined(__GNUC__)
#if defined(__Bitrig__) || defined(__OpenBSD__)
#elif defined(ARCHITECTURE_ARM) && (__ARM_ARCH >= 6)
inline u16 swap16(u16 _data) {
u32 data = _data;
__asm__("rev16 %0, %1\n" : "=l"(data) : "l"(data));
return (u16)data;
}
inline u32 swap32(u32 _data) {
__asm__("rev %0, %1\n" : "=l"(_data) : "l"(_data));
return _data;
}
inline u64 swap64(u64 _data) {
return ((u64)swap32(_data) << 32) | swap32(_data >> 32);
}
#elif __linux__
inline u16 swap16(u16 _data) {
return bswap_16(_data);
}
inline u32 swap32(u32 _data) {
return bswap_32(_data);
}
inline u64 swap64(u64 _data) {
return bswap_64(_data);
}
#elif __APPLE__
inline __attribute__((always_inline)) u16 swap16(u16 _data) {
return (_data >> 8) | (_data << 8);
}
inline __attribute__((always_inline)) u32 swap32(u32 _data) {
return __builtin_bswap32(_data);
}
inline __attribute__((always_inline)) u64 swap64(u64 _data) {
return __builtin_bswap64(_data);
}
#elif defined(__Bitrig__) || defined(__OpenBSD__)
// redefine swap16, swap32, swap64 as inline functions
#undef swap16
#undef swap32
#undef swap64
#endif
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
return __builtin_bswap16(data);
inline u16 swap16(u16 _data) {
return __swap16(_data);
}
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return __builtin_bswap32(data);
inline u32 swap32(u32 _data) {
return __swap32(_data);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return __builtin_bswap64(data);
inline u64 swap64(u64 _data) {
return __swap64(_data);
}
#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
inline u16 swap16(u16 _data) {
return bswap16(_data);
}
inline u32 swap32(u32 _data) {
return bswap32(_data);
}
inline u64 swap64(u64 _data) {
return bswap64(_data);
}
#else
// Generic implementation.
[[nodiscard]] inline u16 swap16(u16 data) noexcept {
// Slow generic implementation.
inline u16 swap16(u16 data) {
return (data >> 8) | (data << 8);
}
[[nodiscard]] inline u32 swap32(u32 data) noexcept {
return ((data & 0xFF000000U) >> 24) | ((data & 0x00FF0000U) >> 8) |
((data & 0x0000FF00U) << 8) | ((data & 0x000000FFU) << 24);
inline u32 swap32(u32 data) {
return (swap16(data) << 16) | swap16(data >> 16);
}
[[nodiscard]] inline u64 swap64(u64 data) noexcept {
return ((data & 0xFF00000000000000ULL) >> 56) | ((data & 0x00FF000000000000ULL) >> 40) |
((data & 0x0000FF0000000000ULL) >> 24) | ((data & 0x000000FF00000000ULL) >> 8) |
((data & 0x00000000FF000000ULL) << 8) | ((data & 0x0000000000FF0000ULL) << 24) |
((data & 0x000000000000FF00ULL) << 40) | ((data & 0x00000000000000FFULL) << 56);
inline u64 swap64(u64 data) {
return ((u64)swap32(data) << 32) | swap32(data >> 32);
}
#endif
[[nodiscard]] inline float swapf(float f) noexcept {
inline float swapf(float f) {
static_assert(sizeof(u32) == sizeof(float), "float must be the same size as uint32_t.");
u32 value;
@@ -111,7 +153,7 @@ namespace Common {
return f;
}
[[nodiscard]] inline double swapd(double f) noexcept {
inline double swapd(double f) {
static_assert(sizeof(u64) == sizeof(double), "double must be the same size as uint64_t.");
u64 value;

View File

@@ -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();

View File

@@ -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()));
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -2,12 +2,44 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
namespace OpenGL::GLShader {
using Tegra::Engines::Maxwell3D;
ProgramManager::ProgramManager() {
pipeline.Create();
}
ProgramManager::~ProgramManager() = default;
void ProgramManager::ApplyTo(OpenGLState& state) {
UpdatePipeline();
state.draw.shader_program = 0;
state.draw.program_pipeline = pipeline.handle;
}
void ProgramManager::UpdatePipeline() {
// Avoid updating the pipeline when values have no changed
if (old_state == current_state) {
return;
}
// Workaround for AMD bug
constexpr GLenum all_used_stages{GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT |
GL_FRAGMENT_SHADER_BIT};
glUseProgramStages(pipeline.handle, all_used_stages, 0);
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, current_state.vertex_shader);
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, current_state.geometry_shader);
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, current_state.fragment_shader);
old_state = current_state;
}
void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shader_stage) {
const auto& regs = maxwell.regs;
const auto& state = maxwell.state;
@@ -16,7 +48,7 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D& maxwell, std::size_t shade
viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
u32 func = static_cast<u32>(regs.alpha_test_func);
auto func{static_cast<u32>(regs.alpha_test_func)};
// Normalize the gl variants of opCompare to be the same as the normal variants
const u32 op_gl_variant_base = static_cast<u32>(Maxwell3D::Regs::ComparisonOp::Never);
if (func >= op_gl_variant_base) {

View File

@@ -4,6 +4,8 @@
#pragma once
#include <cstddef>
#include <glad/glad.h>
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -38,55 +40,48 @@ static_assert(sizeof(MaxwellUniformData) < 16384,
class ProgramManager {
public:
ProgramManager() {
pipeline.Create();
}
explicit ProgramManager();
~ProgramManager();
void ApplyTo(OpenGLState& state);
void UseProgrammableVertexShader(GLuint program) {
vs = program;
current_state.vertex_shader = program;
}
void UseProgrammableGeometryShader(GLuint program) {
gs = program;
current_state.geometry_shader = program;
}
void UseProgrammableFragmentShader(GLuint program) {
fs = program;
current_state.fragment_shader = program;
}
void UseTrivialGeometryShader() {
gs = 0;
}
void ApplyTo(OpenGLState& state) {
UpdatePipeline();
state.draw.shader_program = 0;
state.draw.program_pipeline = pipeline.handle;
current_state.geometry_shader = 0;
}
private:
void UpdatePipeline() {
// Avoid updating the pipeline when values have no changed
if (old_vs == vs && old_fs == fs && old_gs == gs)
return;
// Workaround for AMD bug
glUseProgramStages(pipeline.handle,
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
0);
struct PipelineState {
bool operator==(const PipelineState& rhs) const {
return vertex_shader == rhs.vertex_shader && fragment_shader == rhs.fragment_shader &&
geometry_shader == rhs.geometry_shader;
}
glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vs);
glUseProgramStages(pipeline.handle, GL_GEOMETRY_SHADER_BIT, gs);
glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fs);
bool operator!=(const PipelineState& rhs) const {
return !operator==(rhs);
}
// Update the old values
old_vs = vs;
old_fs = fs;
old_gs = gs;
}
GLuint vertex_shader{};
GLuint fragment_shader{};
GLuint geometry_shader{};
};
void UpdatePipeline();
OGLPipeline pipeline;
GLuint vs{}, fs{}, gs{};
GLuint old_vs{}, old_fs{}, old_gs{};
PipelineState current_state;
PipelineState old_state;
};
} // namespace OpenGL::GLShader

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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");
}
}

View File

@@ -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;
};

View File

@@ -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();

View File

@@ -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];