diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 879738525a..ddbce64215 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -633,10 +633,10 @@ namespace rsx program_hash_util::fragment_program_utils::fragment_program_metadata current_fp_metadata = {}; program_hash_util::vertex_program_utils::vertex_program_metadata current_vp_metadata = {}; - protected: std::array get_color_surface_addresses() const; u32 get_zeta_surface_address() const; + protected: void get_framebuffer_layout(rsx::framebuffer_creation_context context, framebuffer_layout &layout); bool get_scissor(areau& region, bool clip_viewport); diff --git a/rpcs3/rpcs3qt/rsx_debugger.cpp b/rpcs3/rpcs3qt/rsx_debugger.cpp index a2b87cd4e4..8c44a241e4 100644 --- a/rpcs3/rpcs3qt/rsx_debugger.cpp +++ b/rpcs3/rpcs3qt/rsx_debugger.cpp @@ -12,7 +12,11 @@ #include #include #include +#include #include +#include +#include +#include #include @@ -24,6 +28,8 @@ enum GCMEnumTypes constexpr auto qstr = QString::fromStdString; +LOG_CHANNEL(rsx_debugger, "RSX Debugger"); + namespace utils { template @@ -107,10 +113,10 @@ rsx_debugger::rsx_debugger(std::shared_ptr gui_settings, QWidget* m_list_captured_draw_calls = add_rsx_tab(tr("Captured Draw Calls"), 1); m_list_captured_frame->setHorizontalHeaderLabels(QStringList() << tr("Column")); - m_list_captured_frame->setColumnWidth(0, 720); + m_list_captured_frame->setColumnWidth(0, 540); m_list_captured_draw_calls->setHorizontalHeaderLabels(QStringList() << tr("Draw calls")); - m_list_captured_draw_calls->setColumnWidth(0, 720); + m_list_captured_draw_calls->setColumnWidth(0, 540); // Tools: Tools = Controls + Notebook Tabs QVBoxLayout* vbox_tools = new QVBoxLayout(); @@ -140,6 +146,45 @@ rsx_debugger::rsx_debugger(std::shared_ptr gui_settings, QWidget* m_buffer_stencil = new Buffer(false, 4, tr("Stencil Buffer"), this); m_buffer_tex = new Buffer(true, 4, tr("Texture"), this); + for (Buffer* buf : + { + m_buffer_colorA, m_buffer_colorB, m_buffer_colorC, m_buffer_colorD + , m_buffer_depth, m_buffer_stencil, m_buffer_tex + }) + { + buf->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(buf, &QWidget::customContextMenuRequested, buf, &Buffer::ShowContextMenu); + } + + QGroupBox* tex_idx_group = new QGroupBox(tr("Texture Index or Address / Format Override")); + QVBoxLayout* vlayout_tex_idx = new QVBoxLayout(this); + QHBoxLayout* hbox_idx_line = new QHBoxLayout(this); + QLineEdit* tex_idx_line = new QLineEdit(this); + tex_idx_line->setPlaceholderText("00000000"); + tex_idx_line->setFont(mono); + tex_idx_line->setMaxLength(18); + tex_idx_line->setFixedWidth(75); + tex_idx_line->setFocus(); + tex_idx_line->setValidator(new QRegularExpressionValidator(QRegularExpression("^(0[xX])?0*[a-fA-F0-9]{0,8}$"))); + + QLineEdit* tex_fmt_override_line = new QLineEdit(this); + tex_fmt_override_line->setPlaceholderText("00"); + tex_fmt_override_line->setFont(mono); + tex_fmt_override_line->setMaxLength(18); + tex_fmt_override_line->setFixedWidth(75); + tex_fmt_override_line->setFocus(); + tex_fmt_override_line->setValidator(new QRegularExpressionValidator(QRegularExpression("^(0[xX])?0*[a-fA-F0-9]{0,2}$"))); + + hbox_idx_line->addWidget(tex_idx_line); + hbox_idx_line->addWidget(tex_fmt_override_line); + vlayout_tex_idx->addLayout(hbox_idx_line); + + m_enabled_textures_label = new QLabel(enabled_textures_text, this); + + vlayout_tex_idx->addWidget(m_enabled_textures_label); + tex_idx_group->setLayout(vlayout_tex_idx); + // Merge and display everything QVBoxLayout* vbox_buffers1 = new QVBoxLayout(); vbox_buffers1->addWidget(m_buffer_colorA); @@ -152,6 +197,7 @@ rsx_debugger::rsx_debugger(std::shared_ptr gui_settings, QWidget* vbox_buffers2->addWidget(m_buffer_colorB); vbox_buffers2->addWidget(m_buffer_colorD); vbox_buffers2->addWidget(m_buffer_stencil); + vbox_buffers2->addWidget(tex_idx_group); vbox_buffers2->addStretch(); QHBoxLayout* buffer_layout = new QHBoxLayout(); @@ -175,6 +221,26 @@ rsx_debugger::rsx_debugger(std::shared_ptr gui_settings, QWidget* connect(m_list_captured_draw_calls, &QTableWidget::itemClicked, this, &rsx_debugger::OnClickDrawCalls); + connect(tex_idx_line, &QLineEdit::textChanged, [this](const QString& text) + { + bool ok = false; + const u32 addr = (text.startsWith("0x", Qt::CaseInsensitive) ? text.right(text.size() - 2) : text).toULong(&ok, 16); + if (ok) + { + m_cur_texture = addr; + } + }); + + connect(tex_fmt_override_line, &QLineEdit::textChanged, [this](const QString& text) + { + bool ok = false; + const u32 fmt = (text.startsWith("0x", Qt::CaseInsensitive) ? text.right(text.size() - 2) : text).toULong(&ok, 16); + if (ok) + { + m_texture_format_override = fmt; + } + }); + // Restore header states QVariantMap states = m_gui_settings->GetValue(gui::rsx_states).toMap(); for (int i = 0; i < m_tw_rsx->count(); i++) @@ -184,8 +250,12 @@ rsx_debugger::rsx_debugger(std::shared_ptr gui_settings, QWidget* for (u32 i = 0; i < frame_debug.command_queue.size(); i++) m_list_captured_frame->insertRow(i); - if (!restoreGeometry(m_gui_settings->GetValue(gui::rsx_geometry).toByteArray())) - UpdateInformation(); + restoreGeometry(m_gui_settings->GetValue(gui::rsx_geometry).toByteArray()); + + // Check for updates every ~100 ms + QTimer *timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &rsx_debugger::UpdateInformation); + timer->start(100); } void rsx_debugger::closeEvent(QCloseEvent* event) @@ -253,12 +323,15 @@ Buffer::Buffer(bool isTex, u32 id, const QString& name, QWidget* parent) } // Draws a formatted and buffered inside the Buffer Widget -void Buffer::showImage(const QImage& image) +void Buffer::showImage(QImage&& image) { if (image.isNull()) + { + m_canvas->clear(); return; + } - m_image = image; + m_image = std::move(image); const QImage scaled = m_image.scaled(m_image_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); m_canvas->setPixmap(QPixmap::fromImage(scaled)); @@ -269,10 +342,10 @@ void Buffer::showImage(const QImage& image) setLayout(new_layout); } -void Buffer::ShowWindowed() const +void Buffer::ShowWindowed() { //const auto render = rsx::get_current_renderer(); - if (!g_fxo->is_init()) + if (m_image.isNull()) return; // TODO: Is there any better way to choose the color buffers @@ -285,7 +358,7 @@ void Buffer::ShowWindowed() const // return; //} - gui::utils::show_windowed_image(m_image, title()); + gui::utils::show_windowed_image(m_image.copy(), QString("%1 #%2").arg(title()).arg(m_window_counter++)); //if (m_isTex) //{ @@ -299,6 +372,38 @@ void Buffer::ShowWindowed() const //} } +void Buffer::ShowContextMenu(const QPoint& pos) +{ + if (m_image.isNull()) + return; + + QMenu context_menu(this); + + QAction action(tr("Save Image At"), this); + connect(&action, &QAction::triggered, this, [&]() + { + if (m_image.isNull()) + return; + + const QString path = QFileDialog::getSaveFileName(this, tr("Save Image"), "", "Image (*.png)"); + + if (path.isEmpty()) + return; + + if (m_image.save(path, "PNG", 100)) + { + rsx_debugger.success("Saved image to '%s'", path.toStdString()); + } + else + { + rsx_debugger.error("Failure to save image to '%s'", path.toStdString()); + } + }); + + contextMenu.addAction(&action); + contextMenu.exec(mapToGlobal(pos)); +} + namespace { f32 f16_to_f32(f16 val) @@ -430,7 +535,8 @@ void rsx_debugger::OnClickDrawCalls() if (width && height && !draw_call.depth_stencil[0].empty()) { const std::span orig_buffer = draw_call.depth_stencil[0]; - u8* buffer = static_cast(std::malloc(4ULL * width * height)); + m_buffer_depth->cache.resize(4ULL * width * height); + const auto buffer = m_buffer_depth->cache.data(); if (draw_call.state.surface_depth_fmt() == rsx::surface_depth_format::z24s8) { @@ -462,7 +568,7 @@ void rsx_debugger::OnClickDrawCalls() } } } - m_buffer_depth->showImage(QImage(buffer, static_cast(width), static_cast(height), QImage::Format_RGB32, [](void* buffer){ std::free(buffer); }, buffer)); + m_buffer_depth->showImage(QImage(buffer, static_cast(width), static_cast(height), QImage::Format_RGB32)); } } @@ -539,84 +645,567 @@ void rsx_debugger::GetMemory() const file.write(dump); } - for (u32 i = 0;i < frame_debug.draw_calls.size(); i++) + for (u32 i = 0; i < frame_debug.draw_calls.size(); i++) m_list_captured_draw_calls->setItem(i, 0, new QTableWidgetItem(qstr(frame_debug.draw_calls[i].name))); } void rsx_debugger::GetBuffers() const { const auto render = rsx::get_current_renderer(); - if (!render) + if (!render || !render->is_initialized || !render->local_mem_size || !render->is_paused()) { return; } - // Draw Buffers - // TODO: Currently it only supports color buffers - for (u32 bufferId=0; bufferId < render->display_buffers_count; bufferId++) + const auto addrs = render->get_color_surface_addresses(); + + // Color buffers + for (u32 buffer_it = 0; buffer_it < addrs.size(); buffer_it++) { - const auto buffers = render->display_buffers; - const u32 rsx_buffer_addr = rsx::constants::local_mem_base + buffers[bufferId].offset; + const u32 rsx_buffer_addr = addrs[buffer_it]; + const u32 width = rsx::method_registers.surface_clip_width(); + const u32 pitch = rsx::method_registers.surface_pitch(buffer_it); + const u32 height = rsx::method_registers.surface_clip_height(); - const u32 width = buffers[bufferId].width; - const u32 height = buffers[bufferId].height; + Buffer* panel{}; + switch (buffer_it) + { + case 0: panel = m_buffer_colorA; break; + case 1: panel = m_buffer_colorB; break; + case 2: panel = m_buffer_colorC; break; + default: panel = m_buffer_colorD; break; + } - if (!vm::check_addr(rsx_buffer_addr, vm::page_readable, width * height * 4)) + if (!height || !pitch) + { + panel->showImage(QImage()); continue; + } + + u32 bpp = 4; + QImage::Format format = QImage::Format_RGB32; + const auto fmt = rsx::method_registers.surface_color(); + bool bswap = true; + u32 dst_bpp = 0; + + switch (fmt) + { + //case rsx::surface_color_format::x1r5g5b5_z1r5g5b5: + //case rsx::surface_color_format::x1r5g5b5_o1r5g5b5: + case rsx::surface_color_format::r5g6b5: + { + format = QImage::Format_RGB16; + bpp = 2; + break; + } + //case rsx::surface_color_format::x8r8g8b8_z8r8g8b8: + //case rsx::surface_color_format::x8r8g8b8_o8r8g8b8: + case rsx::surface_color_format::a8r8g8b8: + { + // For now ignore alpha channel because it has a tendency of being 0 + //format = QImage::Format_ARGB32; + break; + } + case rsx::surface_color_format::b8: + { + format = QImage::Format_Grayscale8; + bpp = 1; + break; + } + case rsx::surface_color_format::g8b8: + { + bpp = 2; + dst_bpp = 4; + format = QImage::Format_RGBA8888; + bswap = false; + break; + } + //case rsx::surface_color_format::w16z16y16x16, + //case rsx::surface_color_format::w32z32y32x32, + //case rsx::surface_color_format::x32, + //case rsx::surface_color_format::x8b8g8r8_z8b8g8r8, + //case rsx::surface_color_format::x8b8g8r8_o8b8g8r8, + case rsx::surface_color_format::a8b8g8r8: + { + format = QImage::Format_RGBA8888; + break; + } + default: + { + break; + } + } + + // PS3 buffer size (for memory validation) + const u32 src_mem_size = pitch * (height - 1) + width * bpp; + + if (!src_mem_size || !vm::check_addr(rsx_buffer_addr, vm::page_readable, src_mem_size)) + { + panel->showImage(QImage()); + continue; + } + + // Touch RSX memory to potentially flush GPU memory (must occur in named_thread) + named_thread("RSX Buffer Touch", [&]() + { + for (u32 page_start = rsx_buffer_addr & -4096; page_start < rsx_buffer_addr + src_mem_size; page_start += 4096) + { + static_cast(vm::_ref>(page_start).load()); + } + }); + + bswap ^= std::endian::native == std::endian::big; + + if (!dst_bpp) + { + dst_bpp = bpp; + } const auto rsx_buffer = vm::get_super_ptr(rsx_buffer_addr); + panel->cache.resize(std::max(panel->cache.size(), width * height * 16)); + const auto buffer = panel->cache.data(); - u8* buffer = static_cast(std::malloc(4ULL * width * height)); - - // ABGR to ARGB and flip vertically - for (u32 y = 0; y < height; y++) + if (dst_bpp == bpp && pitch == width * bpp) { - for (u32 i = 0, j = 0; j < width * 4; i += 4, j += 4) + std::memcpy(buffer, rsx_buffer, src_mem_size); + } + else + { + for (u32 y = 0; y < height; y++) { - buffer[i + 0 + y * width * 4] = rsx_buffer[j + 1 + (height - y - 1) * width * 4]; // B - buffer[i + 1 + y * width * 4] = rsx_buffer[j + 2 + (height - y - 1) * width * 4]; // G - buffer[i + 2 + y * width * 4] = rsx_buffer[j + 3 + (height - y - 1) * width * 4]; // R - buffer[i + 3 + y * width * 4] = rsx_buffer[j + 0 + (height - y - 1) * width * 4]; // A + const usz line_start = y * pitch; + std::memcpy(buffer + y * width * dst_bpp, rsx_buffer + line_start, width * bpp); } } - // TODO: Is there any better way to clasify the color buffers? How can we include the depth and stencil buffers? - Buffer* pnl; - switch(bufferId) + switch (fmt) { - case 0: pnl = m_buffer_colorA; break; - case 1: pnl = m_buffer_colorB; break; - case 2: pnl = m_buffer_colorC; break; - default: pnl = m_buffer_colorD; break; + case rsx::surface_color_format::g8b8: + { + for (u32 y = 0; y < height; y++) + { + for (u32 x = pitch - 2; x != -2u; x -= 2) + { + const usz line_start = y * pitch; + + std::memcpy(buffer + line_start * 2 + x * 2 + 1, buffer + line_start * 2 + x, 2); + buffer[line_start * 2 + x * 2 + 0] = 0; + buffer[line_start * 2 + x * 2 + 3] = 0xff; + } + } + + break; } - pnl->showImage(QImage(buffer, width, height, QImage::Format_RGB32, [](void* buffer){ std::free(buffer); }, buffer)); + case rsx::surface_color_format::a8b8g8r8: + { + format = QImage::Format_RGBA8888; + bswap = true; + break; + } + case rsx::surface_color_format::a8r8g8b8: + { + break; + } + default: + { + break; + } + } + + if (bswap && bpp != 1) + { + if (bpp == 2) + { + for (u32 i = 0; i < panel->cache.size(); i += 2) + { + u16 res{}; + std::memcpy(&res, buffer + i, 2); + res = stx::se_storage::swap(res); + std::memcpy(buffer + i, &res, 2); + } + } + else if (ensure(bpp == 4)) + { + for (u32 i = 0; i < panel->cache.size(); i += 4) + { + u32 res{}; + std::memcpy(&res, buffer + i, 4); + res = stx::se_storage::swap(res); + std::memcpy(buffer + i, &res, 4); + } + } + } + + panel->showImage(QImage(panel->cache.data(), width, height, format)); } - // Draw Texture - //if (!render->textures[m_cur_texture].enabled()) - // return; + // One iteration for depth, second for stencil + for (u32 buffer_it = 0; buffer_it < 2; buffer_it++) + { + const u32 rsx_buffer_addr = render->get_zeta_surface_address(); - //u32 offset = render->textures[m_cur_texture].offset(); + const u32 width = rsx::method_registers.surface_clip_width(); + const u32 height = rsx::method_registers.surface_clip_height(); + const u32 pitch = rsx::method_registers.surface_z_pitch(); - //if(!offset) - // return; + u32 bpp = 4; + QImage::Format format = QImage::Format_Grayscale16; + const auto fmt = rsx::method_registers.surface_depth_fmt(); + bool has_stencil = false; + bool is_stencil = buffer_it == 1; + bool is_float = false; - //u8 location = render->textures[m_cur_texture].location(); + switch (fmt) + { + case rsx::surface_depth_format2::z16_float: + { + is_float = true; + [[fallthrough]]; + } + case rsx::surface_depth_format2::z16_uint: + { + if (is_stencil) + { + continue; + } - //if(location > 1) - // return; + bpp = 2; + break; + } + case rsx::surface_depth_format2::z24s8_float: + { + is_float = true; + [[fallthrough]]; + } + case rsx::surface_depth_format2::z24s8_uint: + { + format = QImage::Format_RGB16; + has_stencil = true; + break; + } + default: + { + fmt::throw_exception("Unreachable!"); + break; + } + } - //u32 TexBuffer_addr = rsx::get_address(offset, location); + // PS3 buffer size (for memory validation) + const u32 src_mem_size = pitch * (height - 1) + width * bpp; - //if(!vm::check_addr(TexBuffer_addr)) - // return; + Buffer* panel{}; + switch (buffer_it) + { + case 0: panel = m_buffer_depth; break; + default: panel = m_buffer_stencil; break; + } - //unsigned char* TexBuffer = vm::get_super_ptr(TexBuffer_addr); + if (!height || !src_mem_size || !vm::check_addr(rsx_buffer_addr, vm::page_readable, src_mem_size)) + { + panel->showImage(QImage()); + continue; + } - //const u32 width = render->textures[m_cur_texture].width(); - //const u32 height = render->textures[m_cur_texture].height(); - //unsigned char* buffer = (unsigned char*)malloc(width * height * 3); - //std::memcpy(buffer, vm::base(TexBuffer_addr), width * height * 3); + // Touch RSX memory to potentially flush GPU memory (must occur in named_thread) + named_thread("RSX Buffer Touch", [&]() + { + for (u32 page_start = rsx_buffer_addr & -4096; page_start < rsx_buffer_addr + src_mem_size; page_start += 4096) + { + static_cast(vm::_ref>(page_start).load()); + } + }); - //m_buffer_tex->showImage(QImage(buffer, m_text_width, m_text_height, QImage::Format_RGB32)); + const auto rsx_buffer = vm::get_super_ptr(rsx_buffer_addr); + panel->cache.resize(std::max(panel->cache.size(), width * height * 4)); + const auto buffer = panel->cache.data(); + + if (is_stencil) + { + format = QImage::Format_Grayscale8; + + for (u32 y = 0; y < height; y++) + { + for (u32 x = 0; x < width; x++) + { + const usz line_start = y * pitch; + std::memcpy(buffer + y * width + x, rsx_buffer + line_start + x * 4 + 3, 1); + } + } + } + else if (has_stencil) + { + format = QImage::Format_Grayscale16; + + if (false && is_float) + { + // TODO + } + else + { + for (u32 y = 0; y < height; y++) + { + for (u32 x = 0; x < width; x++) + { + const usz line_start = y * pitch; + + be_t data{}; + std::memcpy(&data, rsx_buffer + line_start + x * 4, 4); + const u16 res = static_cast(u64{data / 256} * 0xFFFF / 0xFFFFFF); + std::memcpy(buffer + y * width * 2 + x * 2, &res, 2); + } + } + } + } + else + { + format = QImage::Format_Grayscale16; + + for (u32 y = 0; y < height; y++) + { + for (u32 x = 0; x < width; x++) + { + be_t data{}; + std::memcpy(&data, rsx_buffer + pitch * y + x * 2, 4); + const u16 res = is_float ? static_cast(f16_to_f32(static_cast(+data))) : +data; + std::memcpy(buffer + y * width * 2 + x * 2, &res, 2); + } + } + } + + panel->showImage(QImage(panel->cache.data(), width, height, format)); + } + + auto& textures = rsx::method_registers.fragment_textures; + + u32 texture_addr = m_cur_texture; + u32 tex_fmt_raw = 0; + u32 tex_fmt = 0; + + u32 width = 1280; + u32 height = 720; + u32 pitch = 0; + + if (texture_addr < 16) + { + if (textures[texture_addr].enabled()) + { + width = textures[texture_addr].width(); + height = textures[texture_addr].height(); + pitch = textures[texture_addr].pitch(); + tex_fmt_raw = textures[texture_addr].format(); + tex_fmt = tex_fmt_raw & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); // TOOD: Support these flags + texture_addr = rsx::get_address(textures[texture_addr].offset(), textures[texture_addr].location(), 1); + } + } + + QString text; + + for (u32 i = 0; i < textures.size(); i++) + { + // Technically it can also check if used by shader but idk + if (textures[i].enabled() && textures[i].width()) + { + if (!text.isEmpty()) + { + text += QStringLiteral(", "); + } + + text += QStringLiteral("%0").arg(i); + } + } + + m_enabled_textures_label->setText(enabled_textures_text + text); + + if (m_texture_format_override) + { + tex_fmt = m_texture_format_override; + } + + u32 bytes_per_block = 4; + u32 pixels_per_block = 1; + QImage::Format format = QImage::Format_RGBA8888; + bool bswap = true; + + // Naturally only a handful of common formats are implemented at the moment + + switch (tex_fmt) + { + case CELL_GCM_TEXTURE_A8R8G8B8: + { + format = QImage::Format_RGBA8888; + break; + } + case CELL_GCM_TEXTURE_B8: + { + format = QImage::Format_Grayscale8; + bytes_per_block = 1; + break; + } + case CELL_GCM_TEXTURE_COMPRESSED_DXT1: + { + bytes_per_block = 8; + pixels_per_block = 16; + break; + } + default: + { + break; + } + } + + const u32 line_bytes_count = width * bytes_per_block / pixels_per_block; + + if (!pitch) + { + pitch = line_bytes_count; + } + + // PS3 buffer size (for memory validation) + const u32 src_mem_size = pitch * (height - 1) + line_bytes_count; + + if (!src_mem_size || !height || !vm::check_addr(texture_addr, vm::page_readable, src_mem_size)) + { + m_buffer_tex->showImage(QImage()); + return; + } + + named_thread("RSX Buffer Touch", [&]() + { + // Must touch every page + for (u32 i = texture_addr & -4096; i < texture_addr + src_mem_size; i += 4096) + { + static_cast(vm::_ref>(i).load()); + } + }); + + const auto rsx_buffer = vm::get_super_ptr(texture_addr); + m_buffer_tex->cache.resize(std::max(m_buffer_tex->cache.size(), width * height * 16 + pitch)); + + const auto buffer = m_buffer_tex->cache.data(); + if (pixels_per_block != 1 || pitch == line_bytes_count) + { + std::memcpy(buffer, rsx_buffer, src_mem_size); + } + else + { + for (u32 y = 0; y < height; y++) + { + std::memcpy(buffer + y * line_bytes_count, rsx_buffer + y * pitch, line_bytes_count); + } + } + + switch (tex_fmt) + { + case CELL_GCM_TEXTURE_COMPRESSED_DXT1: + { + bswap = false; + + // Compressed texture to RGB + for (usz i = 0; i < width * height / 16; i++) + { + le_t color0{}, color1{}; + std::memcpy(&color0, rsx_buffer + i * 8, 2); + std::memcpy(&color1, rsx_buffer + i * 8 + 2, 2); + + be_t control{}; + std::memcpy(&control, rsx_buffer + i * 8 + 4, 4); + + for (u32 sub = 0; sub < 16; sub++) + { + const u32 code = (+control >> (sub * 2)) & 3; + + auto convert_to_rgb888 = [](u32 input) -> std::tuple + { + u32 r = ((input >> 11) % 32) * 255 / 32; + u32 g = ((input >> 5) % 64) * 255 / 64; + u32 b = (input % 32) * 255 / 32; + return std::make_tuple(r, g, b); + }; + + const auto [r0, g0, b0] = convert_to_rgb888(color0); + const auto [r1, g1, b1] = convert_to_rgb888(color1); + + auto compute_color = [&](u32 a0, u32 a1, u8 shift) -> u32 + { + u32 a0_portion = 1; + u32 a1_portion = 1; + + switch (code) + { + case 0: + { + return a0; // Color0 only + } + case 1: + { + return a1; // Color1 only + } + case 2: + case 3: + { + if (a0 > a1) + { + if (code == 3) + { + a1_portion = 2; + } + else + { + a0_portion = 2; + } + } + else if (code == 3) + { + return 0; + } // otherwise is the default values + + break; + } + default: + { + fmt::throw_exception("Unreachable!"); + break; + } + } + + return ((a1 * a1_portion + a0 * a0_portion) / (a0_portion + a1_portion)) << shift; + }; + + const le_t dest_color = compute_color(r0, r1, 0) + compute_color(g0, g1, 8) + compute_color(b0, b1, 16) + (255 << 24); + std::memcpy(buffer + ((i % (width / 4) * 4 + sub % 4) + ((i / (width / 4) * 4 + sub / 4) * width)) * sizeof(u32), &dest_color, 4); + } + } + break; + } + default: + { + break; + } + } + + if (bswap && bytes_per_block != 1 && pixels_per_block == 1) + { + if (bytes_per_block == 2) + { + for (u32 i = 0; i < m_buffer_tex->cache.size(); i += 2) + { + u16 res{}; + std::memcpy(&res, buffer + i, 2); + res = stx::se_storage::swap(res); + std::memcpy(buffer + i, &res, 2); + } + } + else if (ensure(bytes_per_block == 4)) + { + for (u32 i = 0; i < m_buffer_tex->cache.size(); i += 4) + { + u32 res{}; + std::memcpy(&res, buffer + i, 4); + res = stx::se_storage::swap(res); + std::memcpy(buffer + i, &res, 4); + } + } + } + + m_buffer_tex->showImage(QImage(m_buffer_tex->cache.data(), width, height, QImage::Format_RGB32)); } diff --git a/rpcs3/rpcs3qt/rsx_debugger.h b/rpcs3/rpcs3qt/rsx_debugger.h index eb47bc7c14..95ac67ddcd 100644 --- a/rpcs3/rpcs3qt/rsx_debugger.h +++ b/rpcs3/rpcs3qt/rsx_debugger.h @@ -15,24 +15,30 @@ #include class gui_settings; +class QPoint; class Buffer : public QGroupBox { Q_OBJECT - const QSize Panel_Size = QSize(108, 108); - const QSize Texture_Size = QSize(108, 108); + const QSize Panel_Size = QSize(320, 180); + const QSize Texture_Size = QSize(320, 180); u32 m_id; bool m_isTex; QImage m_image; QLabel* m_canvas; QSize m_image_size; + u32 m_window_counter = 1; public: + std::vector cache; + Buffer(bool isTex, u32 id, const QString& name, QWidget* parent = nullptr); - void showImage(const QImage& image = QImage()); - void ShowWindowed() const; + void showImage(QImage&& image); + void ShowWindowed(); + + void ShowContextMenu(const QPoint& pos); }; class rsx_debugger : public QDialog @@ -44,6 +50,8 @@ class rsx_debugger : public QDialog QListWidget* m_list_index_buffer; QTabWidget* m_tw_rsx; + const QString enabled_textures_text = tr(" Enabled Textures Indices: "); + Buffer* m_buffer_colorA; Buffer* m_buffer_colorB; Buffer* m_buffer_colorC; @@ -51,11 +59,13 @@ class rsx_debugger : public QDialog Buffer* m_buffer_depth; Buffer* m_buffer_stencil; Buffer* m_buffer_tex; + QLabel* m_enabled_textures_label; QLabel* m_text_transform_program; QLabel* m_text_shader_program; - uint m_cur_texture = 0; + u32 m_cur_texture = 0; + u32 m_texture_format_override = 0; std::shared_ptr m_gui_settings;