diff --git a/externals/boost b/externals/boost
index d81b92699..2dcb9d979 160000
--- a/externals/boost
+++ b/externals/boost
@@ -1 +1 @@
-Subproject commit d81b9269900ae183d0dc98403eea4c971590a807
+Subproject commit 2dcb9d979665b6aabb1635c617973e02914e60ec
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index 6b6617352..9e2ecd307 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -65,6 +65,7 @@ void Config::ReadValues() {
// Renderer
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", false);
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
+ Settings::values.use_scaled_resolution = sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0);
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index c9b490a00..1f1aa716b 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -46,6 +46,10 @@ use_hw_renderer =
# 0 : Interpreter (slow), 1 (default): JIT (fast)
use_shader_jit =
+# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size.
+# 0 (default): Native, 1: Scaled
+use_scaled_resolution =
+
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
bg_red =
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp
index e363be38a..7dc61fe40 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -45,6 +45,7 @@ void Config::ReadValues() {
qt_config->beginGroup("Renderer");
Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", false).toBool();
Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool();
+ Settings::values.use_scaled_resolution = qt_config->value("use_scaled_resolution", false).toBool();
Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat();
@@ -129,6 +130,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Renderer");
qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer);
qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit);
+ qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution);
// Cast to double because Qt's written float values are not human-readable
qt_config->setValue("bg_red", (double)Settings::values.bg_red);
diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp
index a27d0d26c..62648e665 100644
--- a/src/citra_qt/configure_general.cpp
+++ b/src/citra_qt/configure_general.cpp
@@ -25,6 +25,7 @@ void ConfigureGeneral::setConfiguration() {
ui->region_combobox->setCurrentIndex(Settings::values.region_value);
ui->toogle_hw_renderer->setChecked(Settings::values.use_hw_renderer);
ui->toogle_shader_jit->setChecked(Settings::values.use_shader_jit);
+ ui->toogle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution);
}
void ConfigureGeneral::applyConfiguration() {
@@ -33,5 +34,6 @@ void ConfigureGeneral::applyConfiguration() {
Settings::values.region_value = ui->region_combobox->currentIndex();
Settings::values.use_hw_renderer = ui->toogle_hw_renderer->isChecked();
Settings::values.use_shader_jit = ui->toogle_shader_jit->isChecked();
+ Settings::values.use_scaled_resolution = ui->toogle_scaled_resolution->isChecked();
Settings::Apply();
}
diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui
index 47184c5c6..5eb309793 100644
--- a/src/citra_qt/configure_general.ui
+++ b/src/citra_qt/configure_general.ui
@@ -128,6 +128,13 @@
+ -
+
+
+ Enable scaled resolution
+
+
+
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index e9588cb72..cc51ede0c 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -114,6 +114,7 @@ ResultVal File::SyncRequest() {
return read.Code();
}
cmd_buff[2] = static_cast(*read);
+ Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length);
break;
}
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index 0c655395e..211fcf599 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -4,6 +4,7 @@
#include "common/bit_field.h"
#include "common/microprofile.h"
+#include "common/profiler.h"
#include "core/memory.h"
#include "core/hle/kernel/event.h"
@@ -15,8 +16,6 @@
#include "video_core/gpu_debugger.h"
#include "video_core/debug_utils/debug_utils.h"
-#include "video_core/renderer_base.h"
-#include "video_core/video_core.h"
#include "gsp_gpu.h"
@@ -291,8 +290,6 @@ static void FlushDataCache(Service::Interface* self) {
u32 size = cmd_buff[2];
u32 process = cmd_buff[4];
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(Memory::VirtualToPhysicalAddress(address), size);
-
// TODO(purpasmart96): Verify return header on HW
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@@ -408,6 +405,8 @@ void SignalInterrupt(InterruptId interrupt_id) {
g_interrupt_event->Signal();
}
+MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255));
+
/// Executes the next GSP command
static void ExecuteCommand(const Command& command, u32 thread_id) {
// Utility function to convert register ID to address
@@ -419,18 +418,21 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
// GX request DMA - typically used for copying memory from GSP heap to VRAM
case CommandId::REQUEST_DMA:
- VideoCore::g_renderer->Rasterizer()->FlushRegion(Memory::VirtualToPhysicalAddress(command.dma_request.source_address),
- command.dma_request.size);
+ {
+ MICROPROFILE_SCOPE(GPU_GSP_DMA);
+
+ // TODO: Consider attempting rasterizer-accelerated surface blit if that usage is ever possible/likely
+ Memory::RasterizerFlushRegion(Memory::VirtualToPhysicalAddress(command.dma_request.source_address),
+ command.dma_request.size);
+ Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
+ command.dma_request.size);
memcpy(Memory::GetPointer(command.dma_request.dest_address),
Memory::GetPointer(command.dma_request.source_address),
command.dma_request.size);
SignalInterrupt(InterruptId::DMA);
-
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
- command.dma_request.size);
break;
-
+ }
// TODO: This will need some rework in the future. (why?)
case CommandId::SUBMIT_GPU_CMDLIST:
{
@@ -517,13 +519,8 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
case CommandId::CACHE_FLUSH:
{
- for (auto& region : command.cache_flush.regions) {
- if (region.size == 0)
- break;
-
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(
- Memory::VirtualToPhysicalAddress(region.address), region.size);
- }
+ // NOTE: Rasterizer flushing handled elsewhere in CPU read/write and other GPU handlers
+ // Use command.cache_flush.regions to implement this handler
break;
}
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index 22f373adf..1672ad775 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -12,9 +12,6 @@
#include "core/hle/service/y2r_u.h"
#include "core/hw/y2r.h"
-#include "video_core/renderer_base.h"
-#include "video_core/video_core.h"
-
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace Y2R_U
@@ -262,13 +259,12 @@ static void SetAlpha(Service::Interface* self) {
static void StartConversion(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- HW::Y2R::PerformConversion(conversion);
-
// dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
u32 total_output_size = conversion.input_lines *
(conversion.dst.transfer_unit + conversion.dst.gap);
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(
- Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size);
+ Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size);
+
+ HW::Y2R::PerformConversion(conversion);
LOG_DEBUG(Service_Y2R, "called");
completion_event->Signal();
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 7e2f9cdfa..2fe856293 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -115,21 +115,39 @@ inline void Write(u32 addr, const T data) {
u8* start = Memory::GetPhysicalPointer(config.GetStartAddress());
u8* end = Memory::GetPhysicalPointer(config.GetEndAddress());
- if (config.fill_24bit) {
- // fill with 24-bit values
- for (u8* ptr = start; ptr < end; ptr += 3) {
- ptr[0] = config.value_24bit_r;
- ptr[1] = config.value_24bit_g;
- ptr[2] = config.value_24bit_b;
+ // TODO: Consider always accelerating and returning vector of
+ // regions that the accelerated fill did not cover to
+ // reduce/eliminate the fill that the cpu has to do.
+ // This would also mean that the flush below is not needed.
+ // Fill should first flush all surfaces that touch but are
+ // not completely within the fill range.
+ // Then fill all completely covered surfaces, and return the
+ // regions that were between surfaces or within the touching
+ // ones for cpu to manually fill here.
+ if (!VideoCore::g_renderer->Rasterizer()->AccelerateFill(config)) {
+ Memory::RasterizerFlushAndInvalidateRegion(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress());
+
+ if (config.fill_24bit) {
+ // fill with 24-bit values
+ for (u8* ptr = start; ptr < end; ptr += 3) {
+ ptr[0] = config.value_24bit_r;
+ ptr[1] = config.value_24bit_g;
+ ptr[2] = config.value_24bit_b;
+ }
+ } else if (config.fill_32bit) {
+ // fill with 32-bit values
+ if (end > start) {
+ u32 value = config.value_32bit;
+ size_t len = (end - start) / sizeof(u32);
+ for (size_t i = 0; i < len; ++i)
+ memcpy(&start[i * sizeof(u32)], &value, sizeof(u32));
+ }
+ } else {
+ // fill with 16-bit values
+ u16 value_16bit = config.value_16bit.Value();
+ for (u8* ptr = start; ptr < end; ptr += sizeof(u16))
+ memcpy(ptr, &value_16bit, sizeof(u16));
}
- } else if (config.fill_32bit) {
- // fill with 32-bit values
- for (u32* ptr = (u32*)start; ptr < (u32*)end; ++ptr)
- *ptr = config.value_32bit;
- } else {
- // fill with 16-bit values
- for (u16* ptr = (u16*)start; ptr < (u16*)end; ++ptr)
- *ptr = config.value_16bit;
}
LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress());
@@ -139,8 +157,6 @@ inline void Write(u32 addr, const T data) {
} else {
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1);
}
-
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetStartAddress(), config.GetEndAddress() - config.GetStartAddress());
}
// Reset "trigger" flag and set the "finish" flag
@@ -161,184 +177,185 @@ inline void Write(u32 addr, const T data) {
if (Pica::g_debug_context)
Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr);
- u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress());
- u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress());
+ if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) {
+ u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress());
+ u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress());
- if (config.is_texture_copy) {
- u32 input_width = config.texture_copy.input_width * 16;
- u32 input_gap = config.texture_copy.input_gap * 16;
- u32 output_width = config.texture_copy.output_width * 16;
- u32 output_gap = config.texture_copy.output_gap * 16;
+ if (config.is_texture_copy) {
+ u32 input_width = config.texture_copy.input_width * 16;
+ u32 input_gap = config.texture_copy.input_gap * 16;
+ u32 output_width = config.texture_copy.output_width * 16;
+ u32 output_gap = config.texture_copy.output_gap * 16;
- size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap);
- VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size);
+ size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap);
+ Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size);
- u32 remaining_size = config.texture_copy.size;
- u32 remaining_input = input_width;
- u32 remaining_output = output_width;
- while (remaining_size > 0) {
- u32 copy_size = std::min({ remaining_input, remaining_output, remaining_size });
+ size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap);
+ Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size);
- std::memcpy(dst_pointer, src_pointer, copy_size);
- src_pointer += copy_size;
- dst_pointer += copy_size;
+ u32 remaining_size = config.texture_copy.size;
+ u32 remaining_input = input_width;
+ u32 remaining_output = output_width;
+ while (remaining_size > 0) {
+ u32 copy_size = std::min({ remaining_input, remaining_output, remaining_size });
- remaining_input -= copy_size;
- remaining_output -= copy_size;
- remaining_size -= copy_size;
+ std::memcpy(dst_pointer, src_pointer, copy_size);
+ src_pointer += copy_size;
+ dst_pointer += copy_size;
- if (remaining_input == 0) {
- remaining_input = input_width;
- src_pointer += input_gap;
- }
- if (remaining_output == 0) {
- remaining_output = output_width;
- dst_pointer += output_gap;
- }
- }
+ remaining_input -= copy_size;
+ remaining_output -= copy_size;
+ remaining_size -= copy_size;
- LOG_TRACE(HW_GPU, "TextureCopy: 0x%X bytes from 0x%08X(%u+%u)-> 0x%08X(%u+%u), flags 0x%08X",
- config.texture_copy.size,
- config.GetPhysicalInputAddress(), input_width, input_gap,
- config.GetPhysicalOutputAddress(), output_width, output_gap,
- config.flags);
-
- size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap);
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size);
-
- GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
- break;
- }
-
- if (config.scaling > config.ScaleXY) {
- LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value());
- UNIMPLEMENTED();
- break;
- }
-
- if (config.input_linear && config.scaling != config.NoScale) {
- LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input");
- UNIMPLEMENTED();
- break;
- }
-
- bool horizontal_scale = config.scaling != config.NoScale;
- bool vertical_scale = config.scaling == config.ScaleXY;
-
- u32 output_width = config.output_width >> horizontal_scale;
- u32 output_height = config.output_height >> vertical_scale;
-
- u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format);
- u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format);
-
- VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), input_size);
-
- for (u32 y = 0; y < output_height; ++y) {
- for (u32 x = 0; x < output_width; ++x) {
- Math::Vec4 src_color;
-
- // Calculate the [x,y] position of the input image
- // based on the current output position and the scale
- u32 input_x = x << horizontal_scale;
- u32 input_y = y << vertical_scale;
-
- if (config.flip_vertically) {
- // Flip the y value of the output data,
- // we do this after calculating the [x,y] position of the input image
- // to account for the scaling options.
- y = output_height - y - 1;
- }
-
- u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format);
- u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format);
- u32 src_offset;
- u32 dst_offset;
-
- if (config.input_linear) {
- if (!config.dont_swizzle) {
- // Interpret the input as linear and the output as tiled
- u32 coarse_y = y & ~7;
- u32 stride = output_width * dst_bytes_per_pixel;
-
- src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel;
- dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride;
- } else {
- // Both input and output are linear
- src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel;
- dst_offset = (x + y * output_width) * dst_bytes_per_pixel;
+ if (remaining_input == 0) {
+ remaining_input = input_width;
+ src_pointer += input_gap;
}
- } else {
- if (!config.dont_swizzle) {
- // Interpret the input as tiled and the output as linear
- u32 coarse_y = input_y & ~7;
- u32 stride = config.input_width * src_bytes_per_pixel;
-
- src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride;
- dst_offset = (x + y * output_width) * dst_bytes_per_pixel;
- } else {
- // Both input and output are tiled
- u32 out_coarse_y = y & ~7;
- u32 out_stride = output_width * dst_bytes_per_pixel;
-
- u32 in_coarse_y = input_y & ~7;
- u32 in_stride = config.input_width * src_bytes_per_pixel;
-
- src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + in_coarse_y * in_stride;
- dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + out_coarse_y * out_stride;
+ if (remaining_output == 0) {
+ remaining_output = output_width;
+ dst_pointer += output_gap;
}
}
- const u8* src_pixel = src_pointer + src_offset;
- src_color = DecodePixel(config.input_format, src_pixel);
- if (config.scaling == config.ScaleX) {
- Math::Vec4 pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel);
- src_color = ((src_color + pixel) / 2).Cast();
- } else if (config.scaling == config.ScaleXY) {
- Math::Vec4 pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel);
- Math::Vec4 pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel);
- Math::Vec4 pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel);
- src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast();
- }
+ LOG_TRACE(HW_GPU, "TextureCopy: 0x%X bytes from 0x%08X(%u+%u)-> 0x%08X(%u+%u), flags 0x%08X",
+ config.texture_copy.size,
+ config.GetPhysicalInputAddress(), input_width, input_gap,
+ config.GetPhysicalOutputAddress(), output_width, output_gap,
+ config.flags);
- u8* dst_pixel = dst_pointer + dst_offset;
- switch (config.output_format) {
- case Regs::PixelFormat::RGBA8:
- Color::EncodeRGBA8(src_color, dst_pixel);
- break;
+ GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
+ break;
+ }
- case Regs::PixelFormat::RGB8:
- Color::EncodeRGB8(src_color, dst_pixel);
- break;
+ if (config.scaling > config.ScaleXY) {
+ LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value());
+ UNIMPLEMENTED();
+ break;
+ }
- case Regs::PixelFormat::RGB565:
- Color::EncodeRGB565(src_color, dst_pixel);
- break;
+ if (config.input_linear && config.scaling != config.NoScale) {
+ LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input");
+ UNIMPLEMENTED();
+ break;
+ }
- case Regs::PixelFormat::RGB5A1:
- Color::EncodeRGB5A1(src_color, dst_pixel);
- break;
+ int horizontal_scale = config.scaling != config.NoScale ? 1 : 0;
+ int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0;
- case Regs::PixelFormat::RGBA4:
- Color::EncodeRGBA4(src_color, dst_pixel);
- break;
+ u32 output_width = config.output_width >> horizontal_scale;
+ u32 output_height = config.output_height >> vertical_scale;
- default:
- LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value());
- break;
+ u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format);
+ u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format);
+
+ Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size);
+ Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size);
+
+ for (u32 y = 0; y < output_height; ++y) {
+ for (u32 x = 0; x < output_width; ++x) {
+ Math::Vec4 src_color;
+
+ // Calculate the [x,y] position of the input image
+ // based on the current output position and the scale
+ u32 input_x = x << horizontal_scale;
+ u32 input_y = y << vertical_scale;
+
+ if (config.flip_vertically) {
+ // Flip the y value of the output data,
+ // we do this after calculating the [x,y] position of the input image
+ // to account for the scaling options.
+ y = output_height - y - 1;
+ }
+
+ u32 dst_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.output_format);
+ u32 src_bytes_per_pixel = GPU::Regs::BytesPerPixel(config.input_format);
+ u32 src_offset;
+ u32 dst_offset;
+
+ if (config.input_linear) {
+ if (!config.dont_swizzle) {
+ // Interpret the input as linear and the output as tiled
+ u32 coarse_y = y & ~7;
+ u32 stride = output_width * dst_bytes_per_pixel;
+
+ src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel;
+ dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + coarse_y * stride;
+ } else {
+ // Both input and output are linear
+ src_offset = (input_x + input_y * config.input_width) * src_bytes_per_pixel;
+ dst_offset = (x + y * output_width) * dst_bytes_per_pixel;
+ }
+ } else {
+ if (!config.dont_swizzle) {
+ // Interpret the input as tiled and the output as linear
+ u32 coarse_y = input_y & ~7;
+ u32 stride = config.input_width * src_bytes_per_pixel;
+
+ src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + coarse_y * stride;
+ dst_offset = (x + y * output_width) * dst_bytes_per_pixel;
+ } else {
+ // Both input and output are tiled
+ u32 out_coarse_y = y & ~7;
+ u32 out_stride = output_width * dst_bytes_per_pixel;
+
+ u32 in_coarse_y = input_y & ~7;
+ u32 in_stride = config.input_width * src_bytes_per_pixel;
+
+ src_offset = VideoCore::GetMortonOffset(input_x, input_y, src_bytes_per_pixel) + in_coarse_y * in_stride;
+ dst_offset = VideoCore::GetMortonOffset(x, y, dst_bytes_per_pixel) + out_coarse_y * out_stride;
+ }
+ }
+
+ const u8* src_pixel = src_pointer + src_offset;
+ src_color = DecodePixel(config.input_format, src_pixel);
+ if (config.scaling == config.ScaleX) {
+ Math::Vec4 pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel);
+ src_color = ((src_color + pixel) / 2).Cast();
+ } else if (config.scaling == config.ScaleXY) {
+ Math::Vec4 pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel);
+ Math::Vec4 pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel);
+ Math::Vec4 pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel);
+ src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast();
+ }
+
+ u8* dst_pixel = dst_pointer + dst_offset;
+ switch (config.output_format) {
+ case Regs::PixelFormat::RGBA8:
+ Color::EncodeRGBA8(src_color, dst_pixel);
+ break;
+
+ case Regs::PixelFormat::RGB8:
+ Color::EncodeRGB8(src_color, dst_pixel);
+ break;
+
+ case Regs::PixelFormat::RGB565:
+ Color::EncodeRGB565(src_color, dst_pixel);
+ break;
+
+ case Regs::PixelFormat::RGB5A1:
+ Color::EncodeRGB5A1(src_color, dst_pixel);
+ break;
+
+ case Regs::PixelFormat::RGBA4:
+ Color::EncodeRGBA4(src_color, dst_pixel);
+ break;
+
+ default:
+ LOG_ERROR(HW_GPU, "Unknown destination framebuffer format %x", config.output_format.Value());
+ break;
+ }
}
}
- }
- LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X",
+ LOG_TRACE(HW_GPU, "DisplayTriggerTransfer: 0x%08x bytes from 0x%08x(%ux%u)-> 0x%08x(%ux%u), dst format %x, flags 0x%08X",
config.output_height * output_width * GPU::Regs::BytesPerPixel(config.output_format),
config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(),
config.GetPhysicalOutputAddress(), output_width, output_height,
config.output_format.Value(), config.flags);
+ }
g_regs.display_transfer_config.trigger = 0;
GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF);
-
- VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), output_size);
}
break;
}
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index a00adbf53..da4c345b4 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -78,7 +78,7 @@ struct Regs {
INSERT_PADDING_WORDS(0x4);
- struct {
+ struct MemoryFillConfig {
u32 address_start;
u32 address_end;
@@ -165,7 +165,7 @@ struct Regs {
INSERT_PADDING_WORDS(0x169);
- struct {
+ struct DisplayTransferConfig {
u32 input_address;
u32 output_address;
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 7de5bd15d..ee9b69f81 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -15,6 +15,9 @@
#include "core/memory_setup.h"
#include "core/mmio.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
+
namespace Memory {
enum class PageType {
@@ -22,8 +25,12 @@ enum class PageType {
Unmapped,
/// Page is mapped to regular memory. This is the only type you can get pointers to.
Memory,
+ /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and invalidation
+ RasterizerCachedMemory,
/// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
Special,
+ /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and invalidation
+ RasterizerCachedSpecial,
};
struct SpecialRegion {
@@ -57,6 +64,12 @@ struct PageTable {
* the corresponding entry in `pointers` MUST be set to null.
*/
std::array attributes;
+
+ /**
+ * Indicates the number of externally cached resources touching a page that should be
+ * flushed before the memory is accessed
+ */
+ std::array cached_res_count;
};
/// Singular page table used for the singleton process
@@ -72,8 +85,15 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
while (base != end) {
ASSERT_MSG(base < PageTable::NUM_ENTRIES, "out of range mapping at %08X", base);
+ // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be null here
+ if (current_page_table->attributes[base] == PageType::RasterizerCachedMemory ||
+ current_page_table->attributes[base] == PageType::RasterizerCachedSpecial) {
+ RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(base << PAGE_BITS), PAGE_SIZE);
+ }
+
current_page_table->attributes[base] = type;
current_page_table->pointers[base] = memory;
+ current_page_table->cached_res_count[base] = 0;
base += 1;
if (memory != nullptr)
@@ -84,6 +104,7 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
void InitMemoryMap() {
main_page_table.pointers.fill(nullptr);
main_page_table.attributes.fill(PageType::Unmapped);
+ main_page_table.cached_res_count.fill(0);
}
void MapMemoryRegion(VAddr base, u32 size, u8* target) {
@@ -106,6 +127,28 @@ void UnmapRegion(VAddr base, u32 size) {
MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
}
+/**
+ * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned)
+ * using a VMA from the current process
+ */
+static u8* GetPointerFromVMA(VAddr vaddr) {
+ u8* direct_pointer = nullptr;
+
+ auto& vma = Kernel::g_current_process->vm_manager.FindVMA(vaddr)->second;
+ switch (vma.type) {
+ case Kernel::VMAType::AllocatedMemoryBlock:
+ direct_pointer = vma.backing_block->data() + vma.offset;
+ break;
+ case Kernel::VMAType::BackingMemory:
+ direct_pointer = vma.backing_memory;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return direct_pointer + (vaddr - vma.base);
+}
+
/**
* This function should only be called for virtual addreses with attribute `PageType::Special`.
*/
@@ -126,6 +169,7 @@ template
T Read(const VAddr vaddr) {
const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
T value;
std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T));
return value;
@@ -139,8 +183,22 @@ T Read(const VAddr vaddr) {
case PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
+ case PageType::RasterizerCachedMemory:
+ {
+ RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+
+ T value;
+ std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
+ return value;
+ }
case PageType::Special:
return ReadMMIO(GetMMIOHandler(vaddr), vaddr);
+ case PageType::RasterizerCachedSpecial:
+ {
+ RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+
+ return ReadMMIO(GetMMIOHandler(vaddr), vaddr);
+ }
default:
UNREACHABLE();
}
@@ -153,6 +211,7 @@ template
void Write(const VAddr vaddr, const T data) {
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T));
return;
}
@@ -165,9 +224,23 @@ void Write(const VAddr vaddr, const T data) {
case PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
+ case PageType::RasterizerCachedMemory:
+ {
+ RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+
+ std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
+ break;
+ }
case PageType::Special:
WriteMMIO(GetMMIOHandler(vaddr), vaddr, data);
break;
+ case PageType::RasterizerCachedSpecial:
+ {
+ RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+
+ WriteMMIO(GetMMIOHandler(vaddr), vaddr, data);
+ break;
+ }
default:
UNREACHABLE();
}
@@ -179,6 +252,10 @@ u8* GetPointer(const VAddr vaddr) {
return page_pointer + (vaddr & PAGE_MASK);
}
+ if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
+ return GetPointerFromVMA(vaddr);
+ }
+
LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%08x", vaddr);
return nullptr;
}
@@ -187,6 +264,69 @@ u8* GetPhysicalPointer(PAddr address) {
return GetPointer(PhysicalToVirtualAddress(address));
}
+void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
+ if (start == 0) {
+ return;
+ }
+
+ u32 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1;
+ PAddr paddr = start;
+
+ for (unsigned i = 0; i < num_pages; ++i) {
+ VAddr vaddr = PhysicalToVirtualAddress(paddr);
+ u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS];
+ ASSERT_MSG(count_delta <= UINT8_MAX - res_count, "Rasterizer resource cache counter overflow!");
+ ASSERT_MSG(count_delta >= -res_count, "Rasterizer resource cache counter underflow!");
+
+ // Switch page type to cached if now cached
+ if (res_count == 0) {
+ PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ switch (page_type) {
+ case PageType::Memory:
+ page_type = PageType::RasterizerCachedMemory;
+ current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
+ break;
+ case PageType::Special:
+ page_type = PageType::RasterizerCachedSpecial;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ res_count += count_delta;
+
+ // Switch page type to uncached if now uncached
+ if (res_count == 0) {
+ PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ switch (page_type) {
+ case PageType::RasterizerCachedMemory:
+ page_type = PageType::Memory;
+ current_page_table->pointers[vaddr >> PAGE_BITS] = GetPointerFromVMA(vaddr & ~PAGE_MASK);
+ break;
+ case PageType::RasterizerCachedSpecial:
+ page_type = PageType::Special;
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+ paddr += PAGE_SIZE;
+ }
+}
+
+void RasterizerFlushRegion(PAddr start, u32 size) {
+ if (VideoCore::g_renderer != nullptr) {
+ VideoCore::g_renderer->Rasterizer()->FlushRegion(start, size);
+ }
+}
+
+void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) {
+ if (VideoCore::g_renderer != nullptr) {
+ VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size);
+ }
+}
+
u8 Read8(const VAddr addr) {
return Read(addr);
}
diff --git a/src/core/memory.h b/src/core/memory.h
index 5af72b7a7..9caa3c3f5 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -148,4 +148,20 @@ VAddr PhysicalToVirtualAddress(PAddr addr);
*/
u8* GetPhysicalPointer(PAddr address);
+/**
+ * Adds the supplied value to the rasterizer resource cache counter of each
+ * page touching the region.
+ */
+void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta);
+
+/**
+ * Flushes any externally cached rasterizer resources touching the given region.
+ */
+void RasterizerFlushRegion(PAddr start, u32 size);
+
+/**
+ * Flushes and invalidates any externally cached rasterizer resources touching the given region.
+ */
+void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
+
}
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 1aa26fbd2..eaf5c8461 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -19,7 +19,7 @@ void Apply() {
VideoCore::g_hw_renderer_enabled = values.use_hw_renderer;
VideoCore::g_shader_jit_enabled = values.use_shader_jit;
-
+ VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution;
}
} // namespace
diff --git a/src/core/settings.h b/src/core/settings.h
index 4933a516d..d620d8461 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -55,6 +55,7 @@ struct Values {
// Renderer
bool use_hw_renderer;
bool use_shader_jit;
+ bool use_scaled_resolution;
float bg_red;
float bg_green;
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index c3a9c9598..1f058c4e2 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -47,8 +47,8 @@ void DebugContext::OnEvent(Event event, void* data) {
{
std::unique_lock lock(breakpoint_mutex);
- // Commit the hardware renderer's framebuffer so it will show on debug widgets
- VideoCore::g_renderer->Rasterizer()->FlushFramebuffer();
+ // Commit the rasterizer's caches so framebuffers, render targets, etc. will show on debug widgets
+ VideoCore::g_renderer->Rasterizer()->FlushAll();
// TODO: Should stop the CPU thread here once we multithread emulation.
diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index 4552ff81c..1810eca98 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -577,7 +577,7 @@ struct Regs {
}
}
- struct {
+ struct FramebufferConfig {
INSERT_PADDING_WORDS(0x3);
union {
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 008c5827b..bf7101665 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -6,6 +6,10 @@
#include "common/common_types.h"
+#include "core/hw/gpu.h"
+
+struct ScreenInfo;
+
namespace Pica {
namespace Shader {
struct OutputVertex;
@@ -18,12 +22,6 @@ class RasterizerInterface {
public:
virtual ~RasterizerInterface() {}
- /// Initialize API-specific GPU objects
- virtual void InitObjects() = 0;
-
- /// Reset the rasterizer, such as flushing all caches and updating all state
- virtual void Reset() = 0;
-
/// Queues the primitive formed by the given vertices for rendering
virtual void AddTriangle(const Pica::Shader::OutputVertex& v0,
const Pica::Shader::OutputVertex& v1,
@@ -32,17 +30,26 @@ public:
/// Draw the current batch of triangles
virtual void DrawTriangles() = 0;
- /// Commit the rasterizer's framebuffer contents immediately to the current 3DS memory framebuffer
- virtual void FlushFramebuffer() = 0;
-
/// Notify rasterizer that the specified PICA register has been changed
virtual void NotifyPicaRegisterChanged(u32 id) = 0;
- /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory.
+ /// Notify rasterizer that all caches should be flushed to 3DS memory
+ virtual void FlushAll() = 0;
+
+ /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory
virtual void FlushRegion(PAddr addr, u32 size) = 0;
- /// Notify rasterizer that any caches of the specified region should be discraded and reloaded from 3DS memory.
- virtual void InvalidateRegion(PAddr addr, u32 size) = 0;
+ /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory and invalidated
+ virtual void FlushAndInvalidateRegion(PAddr addr, u32 size) = 0;
+
+ /// Attempt to use a faster method to perform a display transfer
+ virtual bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { return false; }
+
+ /// Attempt to use a faster method to fill a region
+ virtual bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { return false; }
+
+ /// Attempt to use a faster method to display the framebuffer to screen
+ virtual bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { return false; }
};
}
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 101f84eb9..ccd497de0 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -21,7 +21,5 @@ void RendererBase::RefreshRasterizerSetting() {
} else {
rasterizer = std::make_unique();
}
- rasterizer->InitObjects();
- rasterizer->Reset();
}
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 6ca9f45e2..da4121c35 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -36,10 +36,7 @@ static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) {
stage.GetAlphaMultiplier() == 1);
}
-RasterizerOpenGL::RasterizerOpenGL() : cached_fb_color_addr(0), cached_fb_depth_addr(0) { }
-RasterizerOpenGL::~RasterizerOpenGL() { }
-
-void RasterizerOpenGL::InitObjects() {
+RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
// Create sampler objects
for (size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
@@ -61,6 +58,10 @@ void RasterizerOpenGL::InitObjects() {
uniform_block_data.dirty = true;
+ for (unsigned index = 0; index < lighting_luts.size(); index++) {
+ uniform_block_data.lut_dirty[index] = true;
+ }
+
// Set vertex attributes
glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position));
glEnableVertexAttribArray(GLShader::ATTRIBUTE_POSITION);
@@ -81,70 +82,24 @@ void RasterizerOpenGL::InitObjects() {
glVertexAttribPointer(GLShader::ATTRIBUTE_VIEW, 3, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, view));
glEnableVertexAttribArray(GLShader::ATTRIBUTE_VIEW);
- SetShader();
-
- // Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation
- fb_color_texture.texture.Create();
- ReconfigureColorTexture(fb_color_texture, Pica::Regs::ColorFormat::RGBA8, 1, 1);
-
- state.texture_units[0].texture_2d = fb_color_texture.texture.handle;
- state.Apply();
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-
- fb_depth_texture.texture.Create();
- ReconfigureDepthTexture(fb_depth_texture, Pica::Regs::DepthFormat::D16, 1, 1);
-
- state.texture_units[0].texture_2d = fb_depth_texture.texture.handle;
- state.Apply();
-
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-
- // Configure OpenGL framebuffer
+ // Create render framebuffer
framebuffer.Create();
- state.draw.framebuffer = framebuffer.handle;
+ // Allocate and bind lighting lut textures
+ for (size_t i = 0; i < lighting_luts.size(); ++i) {
+ lighting_luts[i].Create();
+ state.lighting_luts[i].texture_1d = lighting_luts[i].handle;
+ }
state.Apply();
- glActiveTexture(GL_TEXTURE0);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_color_texture.texture.handle, 0);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fb_depth_texture.texture.handle, 0);
-
- for (size_t i = 0; i < lighting_lut.size(); ++i) {
- lighting_lut[i].Create();
- state.lighting_lut[i].texture_1d = lighting_lut[i].handle;
-
+ for (size_t i = 0; i < lighting_luts.size(); ++i) {
glActiveTexture(GL_TEXTURE3 + i);
- glBindTexture(GL_TEXTURE_1D, state.lighting_lut[i].texture_1d);
-
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
- state.Apply();
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- ASSERT_MSG(status == GL_FRAMEBUFFER_COMPLETE,
- "OpenGL rasterizer framebuffer setup failed, status %X", status);
-}
-
-void RasterizerOpenGL::Reset() {
+ // Sync fixed function OpenGL state
SyncCullMode();
SyncDepthModifiers();
SyncBlendEnabled();
@@ -156,10 +111,10 @@ void RasterizerOpenGL::Reset() {
SyncColorWriteMask();
SyncStencilWriteMask();
SyncDepthWriteMask();
+}
- SetShader();
+RasterizerOpenGL::~RasterizerOpenGL() {
- res_cache.InvalidateAll();
}
/**
@@ -196,47 +151,98 @@ void RasterizerOpenGL::DrawTriangles() {
if (vertex_batch.empty())
return;
- SyncFramebuffer();
- SyncDrawState();
+ const auto& regs = Pica::g_state.regs;
- if (state.draw.shader_dirty) {
- SetShader();
- state.draw.shader_dirty = false;
+ // Sync and bind the framebuffer surfaces
+ CachedSurface* color_surface;
+ CachedSurface* depth_surface;
+ MathUtil::Rectangle rect;
+ std::tie(color_surface, depth_surface, rect) = res_cache.GetFramebufferSurfaces(regs.framebuffer);
+
+ state.draw.draw_framebuffer = framebuffer.handle;
+ state.Apply();
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_surface != nullptr ? color_surface->texture.handle : 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_surface != nullptr ? depth_surface->texture.handle : 0, 0);
+ bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8;
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0);
+
+ if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return;
}
- for (unsigned index = 0; index < lighting_lut.size(); index++) {
+ // Sync the viewport
+ // These registers hold half-width and half-height, so must be multiplied by 2
+ GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2;
+ GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2;
+
+ glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width),
+ (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height),
+ (GLsizei)(viewport_width * color_surface->res_scale_width), (GLsizei)(viewport_height * color_surface->res_scale_height));
+
+ // Sync and bind the texture surfaces
+ const auto pica_textures = regs.GetTextures();
+ for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
+ const auto& texture = pica_textures[texture_index];
+
+ if (texture.enabled) {
+ texture_samplers[texture_index].SyncWithConfig(texture.config);
+ CachedSurface* surface = res_cache.GetTextureSurface(texture);
+ if (surface != nullptr) {
+ state.texture_units[texture_index].texture_2d = surface->texture.handle;
+ } else {
+ // Can occur when texture addr is null or its memory is unmapped/invalid
+ state.texture_units[texture_index].texture_2d = 0;
+ }
+ } else {
+ state.texture_units[texture_index].texture_2d = 0;
+ }
+ }
+
+ // Sync and bind the shader
+ if (shader_dirty) {
+ SetShader();
+ shader_dirty = false;
+ }
+
+ // Sync the lighting luts
+ for (unsigned index = 0; index < lighting_luts.size(); index++) {
if (uniform_block_data.lut_dirty[index]) {
SyncLightingLUT(index);
uniform_block_data.lut_dirty[index] = false;
}
}
+ // Sync the uniform data
if (uniform_block_data.dirty) {
glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), &uniform_block_data.data, GL_STATIC_DRAW);
uniform_block_data.dirty = false;
}
+ state.Apply();
+
+ // Draw the vertex batch
glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), GL_STREAM_DRAW);
glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size());
+ // Mark framebuffer surfaces as dirty
+ // TODO: Restrict invalidation area to the viewport
+ if (color_surface != nullptr) {
+ color_surface->dirty = true;
+ res_cache.FlushRegion(color_surface->addr, color_surface->size, color_surface, true);
+ }
+ if (depth_surface != nullptr) {
+ depth_surface->dirty = true;
+ res_cache.FlushRegion(depth_surface->addr, depth_surface->size, depth_surface, true);
+ }
+
vertex_batch.clear();
- // Flush the resource cache at the current depth and color framebuffer addresses for render-to-texture
- const auto& regs = Pica::g_state.regs;
-
- u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format)
- * fb_color_texture.width * fb_color_texture.height;
-
- u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format)
- * fb_depth_texture.width * fb_depth_texture.height;
-
- res_cache.InvalidateInRange(cached_fb_color_addr, cached_fb_color_size, true);
- res_cache.InvalidateInRange(cached_fb_depth_addr, cached_fb_depth_size, true);
-}
-
-void RasterizerOpenGL::FlushFramebuffer() {
- CommitColorBuffer();
- CommitDepthBuffer();
+ // Unbind textures for potential future use as framebuffer attachments
+ for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
+ state.texture_units[texture_index].texture_2d = 0;
+ }
+ state.Apply();
}
void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
@@ -268,7 +274,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
// Alpha test
case PICA_REG_INDEX(output_merger.alpha_test):
SyncAlphaTest();
- state.draw.shader_dirty = true;
+ shader_dirty = true;
break;
// Sync GL stencil test + stencil write mask
@@ -334,7 +340,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
case PICA_REG_INDEX(tev_stage5.color_op):
case PICA_REG_INDEX(tev_stage5.color_scale):
case PICA_REG_INDEX(tev_combiner_buffer_input):
- state.draw.shader_dirty = true;
+ shader_dirty = true;
break;
case PICA_REG_INDEX(tev_stage0.const_r):
SyncTevConstColor(0, regs.tev_stage0);
@@ -521,41 +527,257 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
}
}
-void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) {
- const auto& regs = Pica::g_state.regs;
-
- u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format)
- * fb_color_texture.width * fb_color_texture.height;
-
- u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format)
- * fb_depth_texture.width * fb_depth_texture.height;
-
- // If source memory region overlaps 3DS framebuffers, commit them before the copy happens
- if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size))
- CommitColorBuffer();
-
- if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size))
- CommitDepthBuffer();
+void RasterizerOpenGL::FlushAll() {
+ res_cache.FlushAll();
}
-void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) {
- const auto& regs = Pica::g_state.regs;
+void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) {
+ res_cache.FlushRegion(addr, size, nullptr, false);
+}
- u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format)
- * fb_color_texture.width * fb_color_texture.height;
+void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
+ res_cache.FlushRegion(addr, size, nullptr, true);
+}
- u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format)
- * fb_depth_texture.width * fb_depth_texture.height;
+bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
+ using PixelFormat = CachedSurface::PixelFormat;
+ using SurfaceType = CachedSurface::SurfaceType;
- // If modified memory region overlaps 3DS framebuffers, reload their contents into OpenGL
- if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size))
- ReloadColorBuffer();
+ if (config.is_texture_copy) {
+ // TODO(tfarley): Try to hardware accelerate this
+ return false;
+ }
- if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size))
- ReloadDepthBuffer();
+ CachedSurface src_params;
+ src_params.addr = config.GetPhysicalInputAddress();
+ src_params.width = config.output_width;
+ src_params.height = config.output_height;
+ src_params.is_tiled = !config.input_linear;
+ src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.input_format);
- // Notify cache of flush in case the region touches a cached resource
- res_cache.InvalidateInRange(addr, size);
+ CachedSurface dst_params;
+ dst_params.addr = config.GetPhysicalOutputAddress();
+ dst_params.width = config.scaling != config.NoScale ? config.output_width / 2 : config.output_width.Value();
+ dst_params.height = config.scaling == config.ScaleXY ? config.output_height / 2 : config.output_height.Value();
+ dst_params.is_tiled = config.input_linear != config.dont_swizzle;
+ dst_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.output_format);
+
+ MathUtil::Rectangle src_rect;
+ CachedSurface* src_surface = res_cache.GetSurfaceRect(src_params, false, true, src_rect);
+
+ if (src_surface == nullptr) {
+ return false;
+ }
+
+ // Require destination surface to have same resolution scale as source to preserve scaling
+ dst_params.res_scale_width = src_surface->res_scale_width;
+ dst_params.res_scale_height = src_surface->res_scale_height;
+
+ MathUtil::Rectangle dst_rect;
+ CachedSurface* dst_surface = res_cache.GetSurfaceRect(dst_params, true, false, dst_rect);
+
+ if (dst_surface == nullptr) {
+ return false;
+ }
+
+ // Don't accelerate if the src and dst surfaces are the same
+ if (src_surface == dst_surface) {
+ return false;
+ }
+
+ if (config.flip_vertically) {
+ std::swap(dst_rect.top, dst_rect.bottom);
+ }
+
+ if (!res_cache.TryBlitSurfaces(src_surface, src_rect, dst_surface, dst_rect)) {
+ return false;
+ }
+
+ u32 dst_size = dst_params.width * dst_params.height * CachedSurface::GetFormatBpp(dst_params.pixel_format) / 8;
+ dst_surface->dirty = true;
+ res_cache.FlushRegion(config.GetPhysicalOutputAddress(), dst_size, dst_surface, true);
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) {
+ using PixelFormat = CachedSurface::PixelFormat;
+ using SurfaceType = CachedSurface::SurfaceType;
+
+ CachedSurface* dst_surface = res_cache.TryGetFillSurface(config);
+
+ if (dst_surface == nullptr) {
+ return false;
+ }
+
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ SurfaceType dst_type = CachedSurface::GetFormatType(dst_surface->pixel_format);
+
+ GLuint old_fb = cur_state.draw.draw_framebuffer;
+ cur_state.draw.draw_framebuffer = framebuffer.handle;
+ // TODO: When scissor test is implemented, need to disable scissor test in cur_state here so Clear call isn't affected
+ cur_state.Apply();
+
+ if (dst_type == SurfaceType::Color || dst_type == SurfaceType::Texture) {
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_surface->texture.handle, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return false;
+ }
+
+ GLfloat color_values[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+
+ // TODO: Handle additional pixel format and fill value size combinations to accelerate more cases
+ // For instance, checking if fill value's bytes/bits repeat to allow filling I8/A8/I4/A4/...
+ // Currently only handles formats that are multiples of the fill value size
+
+ if (config.fill_24bit) {
+ switch (dst_surface->pixel_format) {
+ case PixelFormat::RGB8:
+ color_values[0] = config.value_24bit_r / 255.0f;
+ color_values[1] = config.value_24bit_g / 255.0f;
+ color_values[2] = config.value_24bit_b / 255.0f;
+ break;
+ default:
+ return false;
+ }
+ } else if (config.fill_32bit) {
+ u32 value = config.value_32bit;
+
+ switch (dst_surface->pixel_format) {
+ case PixelFormat::RGBA8:
+ color_values[0] = (value >> 24) / 255.0f;
+ color_values[1] = ((value >> 16) & 0xFF) / 255.0f;
+ color_values[2] = ((value >> 8) & 0xFF) / 255.0f;
+ color_values[3] = (value & 0xFF) / 255.0f;
+ break;
+ default:
+ return false;
+ }
+ } else {
+ u16 value_16bit = config.value_16bit.Value();
+ Math::Vec4 color;
+
+ switch (dst_surface->pixel_format) {
+ case PixelFormat::RGBA8:
+ color_values[0] = (value_16bit >> 8) / 255.0f;
+ color_values[1] = (value_16bit & 0xFF) / 255.0f;
+ color_values[2] = color_values[0];
+ color_values[3] = color_values[1];
+ break;
+ case PixelFormat::RGB5A1:
+ color = Color::DecodeRGB5A1((const u8*)&value_16bit);
+ color_values[0] = color[0] / 31.0f;
+ color_values[1] = color[1] / 31.0f;
+ color_values[2] = color[2] / 31.0f;
+ color_values[3] = color[3];
+ break;
+ case PixelFormat::RGB565:
+ color = Color::DecodeRGB565((const u8*)&value_16bit);
+ color_values[0] = color[0] / 31.0f;
+ color_values[1] = color[1] / 63.0f;
+ color_values[2] = color[2] / 31.0f;
+ break;
+ case PixelFormat::RGBA4:
+ color = Color::DecodeRGBA4((const u8*)&value_16bit);
+ color_values[0] = color[0] / 15.0f;
+ color_values[1] = color[1] / 15.0f;
+ color_values[2] = color[2] / 15.0f;
+ color_values[3] = color[3] / 15.0f;
+ break;
+ case PixelFormat::IA8:
+ case PixelFormat::RG8:
+ color_values[0] = (value_16bit >> 8) / 255.0f;
+ color_values[1] = (value_16bit & 0xFF) / 255.0f;
+ break;
+ default:
+ return false;
+ }
+ }
+
+ cur_state.color_mask.red_enabled = true;
+ cur_state.color_mask.green_enabled = true;
+ cur_state.color_mask.blue_enabled = true;
+ cur_state.color_mask.alpha_enabled = true;
+ cur_state.Apply();
+ glClearBufferfv(GL_COLOR, 0, color_values);
+ } else if (dst_type == SurfaceType::Depth) {
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_surface->texture.handle, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return false;
+ }
+
+ GLfloat value_float;
+ if (dst_surface->pixel_format == CachedSurface::PixelFormat::D16) {
+ value_float = config.value_32bit / 65535.0f; // 2^16 - 1
+ } else if (dst_surface->pixel_format == CachedSurface::PixelFormat::D24) {
+ value_float = config.value_32bit / 16777215.0f; // 2^24 - 1
+ }
+
+ cur_state.depth.write_mask = true;
+ cur_state.Apply();
+ glClearBufferfv(GL_DEPTH, 0, &value_float);
+ } else if (dst_type == SurfaceType::DepthStencil) {
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dst_surface->texture.handle, 0);
+
+ if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return false;
+ }
+
+ GLfloat value_float = (config.value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
+ GLint value_int = (config.value_32bit >> 24);
+
+ cur_state.depth.write_mask = true;
+ cur_state.stencil.write_mask = true;
+ cur_state.Apply();
+ glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int);
+ }
+
+ cur_state.draw.draw_framebuffer = old_fb;
+ // TODO: Return scissor test to previous value when scissor test is implemented
+ cur_state.Apply();
+
+ dst_surface->dirty = true;
+ res_cache.FlushRegion(dst_surface->addr, dst_surface->size, dst_surface, true);
+ return true;
+}
+
+bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) {
+ if (framebuffer_addr == 0) {
+ return false;
+ }
+
+ CachedSurface src_params;
+ src_params.addr = framebuffer_addr;
+ src_params.width = config.width;
+ src_params.height = config.height;
+ src_params.stride = pixel_stride;
+ src_params.is_tiled = false;
+ src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.color_format);
+
+ MathUtil::Rectangle src_rect;
+ CachedSurface* src_surface = res_cache.GetSurfaceRect(src_params, false, true, src_rect);
+
+ if (src_surface == nullptr) {
+ return false;
+ }
+
+ u32 scaled_width = src_surface->GetScaledWidth();
+ u32 scaled_height = src_surface->GetScaledHeight();
+
+ screen_info.display_texcoords = MathUtil::Rectangle((float)src_rect.top / (float)scaled_height,
+ (float)src_rect.left / (float)scaled_width,
+ (float)src_rect.bottom / (float)scaled_height,
+ (float)src_rect.right / (float)scaled_width);
+
+ screen_info.display_texture = src_surface->texture.handle;
+
+ return true;
}
void RasterizerOpenGL::SamplerInfo::Create() {
@@ -597,108 +819,6 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConf
}
}
-void RasterizerOpenGL::ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height) {
- GLint internal_format;
-
- texture.format = format;
- texture.width = width;
- texture.height = height;
-
- switch (format) {
- case Pica::Regs::ColorFormat::RGBA8:
- internal_format = GL_RGBA;
- texture.gl_format = GL_RGBA;
- texture.gl_type = GL_UNSIGNED_INT_8_8_8_8;
- break;
-
- case Pica::Regs::ColorFormat::RGB8:
- // This pixel format uses BGR since GL_UNSIGNED_BYTE specifies byte-order, unlike every
- // specific OpenGL type used in this function using native-endian (that is, little-endian
- // mostly everywhere) for words or half-words.
- // TODO: check how those behave on big-endian processors.
- internal_format = GL_RGB;
- texture.gl_format = GL_BGR;
- texture.gl_type = GL_UNSIGNED_BYTE;
- break;
-
- case Pica::Regs::ColorFormat::RGB5A1:
- internal_format = GL_RGBA;
- texture.gl_format = GL_RGBA;
- texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1;
- break;
-
- case Pica::Regs::ColorFormat::RGB565:
- internal_format = GL_RGB;
- texture.gl_format = GL_RGB;
- texture.gl_type = GL_UNSIGNED_SHORT_5_6_5;
- break;
-
- case Pica::Regs::ColorFormat::RGBA4:
- internal_format = GL_RGBA;
- texture.gl_format = GL_RGBA;
- texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4;
- break;
-
- default:
- LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer texture color format %x", format);
- UNIMPLEMENTED();
- break;
- }
-
- state.texture_units[0].texture_2d = texture.texture.handle;
- state.Apply();
-
- glActiveTexture(GL_TEXTURE0);
- glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0,
- texture.gl_format, texture.gl_type, nullptr);
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-}
-
-void RasterizerOpenGL::ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height) {
- GLint internal_format;
-
- texture.format = format;
- texture.width = width;
- texture.height = height;
-
- switch (format) {
- case Pica::Regs::DepthFormat::D16:
- internal_format = GL_DEPTH_COMPONENT16;
- texture.gl_format = GL_DEPTH_COMPONENT;
- texture.gl_type = GL_UNSIGNED_SHORT;
- break;
-
- case Pica::Regs::DepthFormat::D24:
- internal_format = GL_DEPTH_COMPONENT24;
- texture.gl_format = GL_DEPTH_COMPONENT;
- texture.gl_type = GL_UNSIGNED_INT;
- break;
-
- case Pica::Regs::DepthFormat::D24S8:
- internal_format = GL_DEPTH24_STENCIL8;
- texture.gl_format = GL_DEPTH_STENCIL;
- texture.gl_type = GL_UNSIGNED_INT_24_8;
- break;
-
- default:
- LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer texture depth format %x", format);
- UNIMPLEMENTED();
- break;
- }
-
- state.texture_units[0].texture_2d = texture.texture.handle;
- state.Apply();
-
- glActiveTexture(GL_TEXTURE0);
- glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0,
- texture.gl_format, texture.gl_type, nullptr);
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-}
-
void RasterizerOpenGL::SetShader() {
PicaShaderConfig config = PicaShaderConfig::CurrentConfig();
std::unique_ptr shader = std::make_unique();
@@ -761,83 +881,6 @@ void RasterizerOpenGL::SetShader() {
}
}
-void RasterizerOpenGL::SyncFramebuffer() {
- const auto& regs = Pica::g_state.regs;
-
- PAddr new_fb_color_addr = regs.framebuffer.GetColorBufferPhysicalAddress();
- Pica::Regs::ColorFormat new_fb_color_format = regs.framebuffer.color_format;
-
- PAddr new_fb_depth_addr = regs.framebuffer.GetDepthBufferPhysicalAddress();
- Pica::Regs::DepthFormat new_fb_depth_format = regs.framebuffer.depth_format;
-
- bool fb_size_changed = fb_color_texture.width != static_cast(regs.framebuffer.GetWidth()) ||
- fb_color_texture.height != static_cast(regs.framebuffer.GetHeight());
-
- bool color_fb_prop_changed = fb_color_texture.format != new_fb_color_format ||
- fb_size_changed;
-
- bool depth_fb_prop_changed = fb_depth_texture.format != new_fb_depth_format ||
- fb_size_changed;
-
- bool color_fb_modified = cached_fb_color_addr != new_fb_color_addr ||
- color_fb_prop_changed;
-
- bool depth_fb_modified = cached_fb_depth_addr != new_fb_depth_addr ||
- depth_fb_prop_changed;
-
- // Commit if framebuffer modified in any way
- if (color_fb_modified)
- CommitColorBuffer();
-
- if (depth_fb_modified)
- CommitDepthBuffer();
-
- // Reconfigure framebuffer textures if any property has changed
- if (color_fb_prop_changed) {
- ReconfigureColorTexture(fb_color_texture, new_fb_color_format,
- regs.framebuffer.GetWidth(), regs.framebuffer.GetHeight());
- }
-
- if (depth_fb_prop_changed) {
- ReconfigureDepthTexture(fb_depth_texture, new_fb_depth_format,
- regs.framebuffer.GetWidth(), regs.framebuffer.GetHeight());
-
- // Only attach depth buffer as stencil if it supports stencil
- switch (new_fb_depth_format) {
- case Pica::Regs::DepthFormat::D16:
- case Pica::Regs::DepthFormat::D24:
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
- break;
-
- case Pica::Regs::DepthFormat::D24S8:
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fb_depth_texture.texture.handle, 0);
- break;
-
- default:
- LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer depth format %x", new_fb_depth_format);
- UNIMPLEMENTED();
- break;
- }
- }
-
- // Load buffer data again if fb modified in any way
- if (color_fb_modified) {
- cached_fb_color_addr = new_fb_color_addr;
-
- ReloadColorBuffer();
- }
-
- if (depth_fb_modified) {
- cached_fb_depth_addr = new_fb_depth_addr;
-
- ReloadDepthBuffer();
- }
-
- GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- ASSERT_MSG(status == GL_FRAMEBUFFER_COMPLETE,
- "OpenGL rasterizer framebuffer setup failed, status %X", status);
-}
-
void RasterizerOpenGL::SyncCullMode() {
const auto& regs = Pica::g_state.regs;
@@ -1034,229 +1077,3 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) {
uniform_block_data.dirty = true;
}
}
-
-void RasterizerOpenGL::SyncDrawState() {
- const auto& regs = Pica::g_state.regs;
-
- // Sync the viewport
- GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2;
- GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2;
-
- // OpenGL uses different y coordinates, so negate corner offset and flip origin
- // TODO: Ensure viewport_corner.x should not be negated or origin flipped
- // TODO: Use floating-point viewports for accuracy if supported
- glViewport((GLsizei)regs.viewport_corner.x,
- (GLsizei)regs.viewport_corner.y,
- viewport_width, viewport_height);
-
- // Sync bound texture(s), upload if not cached
- const auto pica_textures = regs.GetTextures();
- for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
- const auto& texture = pica_textures[texture_index];
-
- if (texture.enabled) {
- texture_samplers[texture_index].SyncWithConfig(texture.config);
- res_cache.LoadAndBindTexture(state, texture_index, texture);
- } else {
- state.texture_units[texture_index].texture_2d = 0;
- }
- }
-
- state.draw.uniform_buffer = uniform_buffer.handle;
- state.Apply();
-}
-
-MICROPROFILE_DEFINE(OpenGL_FramebufferReload, "OpenGL", "FB Reload", MP_RGB(70, 70, 200));
-
-void RasterizerOpenGL::ReloadColorBuffer() {
- u8* color_buffer = Memory::GetPhysicalPointer(cached_fb_color_addr);
-
- if (color_buffer == nullptr)
- return;
-
- MICROPROFILE_SCOPE(OpenGL_FramebufferReload);
-
- u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format);
-
- std::unique_ptr temp_fb_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]);
-
- // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary.
- for (int y = 0; y < fb_color_texture.height; ++y) {
- for (int x = 0; x < fb_color_texture.width; ++x) {
- const u32 coarse_y = y & ~7;
- u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_color_texture.width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (fb_color_texture.height - 1 - y) * fb_color_texture.width) * bytes_per_pixel;
-
- u8* pixel = color_buffer + dst_offset;
- memcpy(&temp_fb_color_buffer[gl_pixel_index], pixel, bytes_per_pixel);
- }
- }
-
- state.texture_units[0].texture_2d = fb_color_texture.texture.handle;
- state.Apply();
-
- glActiveTexture(GL_TEXTURE0);
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_color_texture.width, fb_color_texture.height,
- fb_color_texture.gl_format, fb_color_texture.gl_type, temp_fb_color_buffer.get());
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-}
-
-void RasterizerOpenGL::ReloadDepthBuffer() {
- if (cached_fb_depth_addr == 0)
- return;
-
- // TODO: Appears to work, but double-check endianness of depth values and order of depth-stencil
- u8* depth_buffer = Memory::GetPhysicalPointer(cached_fb_depth_addr);
-
- if (depth_buffer == nullptr)
- return;
-
- MICROPROFILE_SCOPE(OpenGL_FramebufferReload);
-
- u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format);
-
- // OpenGL needs 4 bpp alignment for D24
- u32 gl_bpp = bytes_per_pixel == 3 ? 4 : bytes_per_pixel;
-
- std::unique_ptr temp_fb_depth_buffer(new u8[fb_depth_texture.width * fb_depth_texture.height * gl_bpp]);
-
- u8* temp_fb_depth_data = bytes_per_pixel == 3 ? (temp_fb_depth_buffer.get() + 1) : temp_fb_depth_buffer.get();
-
- if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) {
- for (int y = 0; y < fb_depth_texture.height; ++y) {
- for (int x = 0; x < fb_depth_texture.width; ++x) {
- const u32 coarse_y = y & ~7;
- u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width);
-
- u8* pixel = depth_buffer + dst_offset;
- u32 depth_stencil = *(u32*)pixel;
- ((u32*)temp_fb_depth_data)[gl_pixel_index] = (depth_stencil << 8) | (depth_stencil >> 24);
- }
- }
- } else {
- for (int y = 0; y < fb_depth_texture.height; ++y) {
- for (int x = 0; x < fb_depth_texture.width; ++x) {
- const u32 coarse_y = y & ~7;
- u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width) * gl_bpp;
-
- u8* pixel = depth_buffer + dst_offset;
- memcpy(&temp_fb_depth_data[gl_pixel_index], pixel, bytes_per_pixel);
- }
- }
- }
-
- state.texture_units[0].texture_2d = fb_depth_texture.texture.handle;
- state.Apply();
-
- glActiveTexture(GL_TEXTURE0);
- if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) {
- // TODO(Subv): There is a bug with Intel Windows drivers that makes glTexSubImage2D not change the stencil buffer.
- // The bug has been reported to Intel (https://communities.intel.com/message/324464)
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, fb_depth_texture.width, fb_depth_texture.height, 0,
- GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, temp_fb_depth_buffer.get());
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_depth_texture.width, fb_depth_texture.height,
- fb_depth_texture.gl_format, fb_depth_texture.gl_type, temp_fb_depth_buffer.get());
- }
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-}
-
-Common::Profiling::TimingCategory buffer_commit_category("Framebuffer Commit");
-MICROPROFILE_DEFINE(OpenGL_FramebufferCommit, "OpenGL", "FB Commit", MP_RGB(70, 70, 200));
-
-void RasterizerOpenGL::CommitColorBuffer() {
- if (cached_fb_color_addr != 0) {
- u8* color_buffer = Memory::GetPhysicalPointer(cached_fb_color_addr);
-
- if (color_buffer != nullptr) {
- Common::Profiling::ScopeTimer timer(buffer_commit_category);
- MICROPROFILE_SCOPE(OpenGL_FramebufferCommit);
-
- u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format);
-
- std::unique_ptr temp_gl_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]);
-
- state.texture_units[0].texture_2d = fb_color_texture.texture.handle;
- state.Apply();
-
- glActiveTexture(GL_TEXTURE0);
- glGetTexImage(GL_TEXTURE_2D, 0, fb_color_texture.gl_format, fb_color_texture.gl_type, temp_gl_color_buffer.get());
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-
- // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary.
- for (int y = 0; y < fb_color_texture.height; ++y) {
- for (int x = 0; x < fb_color_texture.width; ++x) {
- const u32 coarse_y = y & ~7;
- u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_color_texture.width * bytes_per_pixel;
- u32 gl_pixel_index = x * bytes_per_pixel + (fb_color_texture.height - 1 - y) * fb_color_texture.width * bytes_per_pixel;
-
- u8* pixel = color_buffer + dst_offset;
- memcpy(pixel, &temp_gl_color_buffer[gl_pixel_index], bytes_per_pixel);
- }
- }
- }
- }
-}
-
-void RasterizerOpenGL::CommitDepthBuffer() {
- if (cached_fb_depth_addr != 0) {
- // TODO: Output seems correct visually, but doesn't quite match sw renderer output. One of them is wrong.
- u8* depth_buffer = Memory::GetPhysicalPointer(cached_fb_depth_addr);
-
- if (depth_buffer != nullptr) {
- Common::Profiling::ScopeTimer timer(buffer_commit_category);
- MICROPROFILE_SCOPE(OpenGL_FramebufferCommit);
-
- u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format);
-
- // OpenGL needs 4 bpp alignment for D24
- u32 gl_bpp = bytes_per_pixel == 3 ? 4 : bytes_per_pixel;
-
- std::unique_ptr temp_gl_depth_buffer(new u8[fb_depth_texture.width * fb_depth_texture.height * gl_bpp]);
-
- state.texture_units[0].texture_2d = fb_depth_texture.texture.handle;
- state.Apply();
-
- glActiveTexture(GL_TEXTURE0);
- glGetTexImage(GL_TEXTURE_2D, 0, fb_depth_texture.gl_format, fb_depth_texture.gl_type, temp_gl_depth_buffer.get());
-
- state.texture_units[0].texture_2d = 0;
- state.Apply();
-
- u8* temp_gl_depth_data = bytes_per_pixel == 3 ? (temp_gl_depth_buffer.get() + 1) : temp_gl_depth_buffer.get();
-
- if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) {
- for (int y = 0; y < fb_depth_texture.height; ++y) {
- for (int x = 0; x < fb_depth_texture.width; ++x) {
- const u32 coarse_y = y & ~7;
- u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width);
-
- u8* pixel = depth_buffer + dst_offset;
- u32 depth_stencil = ((u32*)temp_gl_depth_data)[gl_pixel_index];
- *(u32*)pixel = (depth_stencil >> 8) | (depth_stencil << 24);
- }
- }
- } else {
- for (int y = 0; y < fb_depth_texture.height; ++y) {
- for (int x = 0; x < fb_depth_texture.width; ++x) {
- const u32 coarse_y = y & ~7;
- u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel;
- u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width) * gl_bpp;
-
- u8* pixel = depth_buffer + dst_offset;
- memcpy(pixel, &temp_gl_depth_data[gl_pixel_index], bytes_per_pixel);
- }
- }
- }
- }
- }
-}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 390349a0c..5aa638985 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -19,6 +19,7 @@
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/pica_to_gl.h"
+#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/shader/shader_interpreter.h"
/**
@@ -191,16 +192,17 @@ public:
RasterizerOpenGL();
~RasterizerOpenGL() override;
- void InitObjects() override;
- void Reset() override;
void AddTriangle(const Pica::Shader::OutputVertex& v0,
const Pica::Shader::OutputVertex& v1,
const Pica::Shader::OutputVertex& v2) override;
void DrawTriangles() override;
- void FlushFramebuffer() override;
void NotifyPicaRegisterChanged(u32 id) override;
+ void FlushAll() override;
void FlushRegion(PAddr addr, u32 size) override;
- void InvalidateRegion(PAddr addr, u32 size) override;
+ void FlushAndInvalidateRegion(PAddr addr, u32 size) override;
+ bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) override;
+ bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) override;
+ bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) override;
/// OpenGL shader generated for a given Pica register state
struct PicaShader {
@@ -210,26 +212,6 @@ public:
private:
- /// Structure used for storing information about color textures
- struct TextureInfo {
- OGLTexture texture;
- GLsizei width;
- GLsizei height;
- Pica::Regs::ColorFormat format;
- GLenum gl_format;
- GLenum gl_type;
- };
-
- /// Structure used for storing information about depth textures
- struct DepthTextureInfo {
- OGLTexture texture;
- GLsizei width;
- GLsizei height;
- Pica::Regs::DepthFormat format;
- GLenum gl_format;
- GLenum gl_type;
- };
-
struct SamplerInfo {
using TextureConfig = Pica::Regs::TextureConfig;
@@ -311,18 +293,9 @@ private:
static_assert(sizeof(UniformData) == 0x310, "The size of the UniformData structure has changed, update the structure in the shader");
static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec");
- /// Reconfigure the OpenGL color texture to use the given format and dimensions
- void ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height);
-
- /// Reconfigure the OpenGL depth texture to use the given format and dimensions
- void ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height);
-
/// Sets the OpenGL shader in accordance with the current PICA register state
void SetShader();
- /// Syncs the state and contents of the OpenGL framebuffer to match the current PICA framebuffer
- void SyncFramebuffer();
-
/// Syncs the cull mode to match the PICA register
void SyncCullMode();
@@ -386,45 +359,15 @@ private:
/// Syncs the specified light's specular 1 color to match the PICA register
void SyncLightSpecular1(int light_index);
- /// Syncs the remaining OpenGL drawing state to match the current PICA state
- void SyncDrawState();
-
- /// Copies the 3DS color framebuffer into the OpenGL color framebuffer texture
- void ReloadColorBuffer();
-
- /// Copies the 3DS depth framebuffer into the OpenGL depth framebuffer texture
- void ReloadDepthBuffer();
-
- /**
- * Save the current OpenGL color framebuffer to the current PICA framebuffer in 3DS memory
- * Loads the OpenGL framebuffer textures into temporary buffers
- * Then copies into the 3DS framebuffer using proper Morton order
- */
- void CommitColorBuffer();
-
- /**
- * Save the current OpenGL depth framebuffer to the current PICA framebuffer in 3DS memory
- * Loads the OpenGL framebuffer textures into temporary buffers
- * Then copies into the 3DS framebuffer using proper Morton order
- */
- void CommitDepthBuffer();
+ OpenGLState state;
RasterizerCacheOpenGL res_cache;
std::vector vertex_batch;
- OpenGLState state;
-
- PAddr cached_fb_color_addr;
- PAddr cached_fb_depth_addr;
-
- // Hardware rasterizer
- std::array texture_samplers;
- TextureInfo fb_color_texture;
- DepthTextureInfo fb_depth_texture;
-
std::unordered_map> shader_cache;
const PicaShader* current_shader = nullptr;
+ bool shader_dirty;
struct {
UniformData data;
@@ -432,11 +375,12 @@ private:
bool dirty;
} uniform_block_data;
+ std::array texture_samplers;
OGLVertexArray vertex_array;
OGLBuffer vertex_buffer;
OGLBuffer uniform_buffer;
OGLFramebuffer framebuffer;
- std::array lighting_lut;
+ std::array lighting_luts;
std::array, 6> lighting_lut_data;
};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 1323c12e4..55c2fb283 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -2,8 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include
+#include
+#include "common/emu_window.h"
#include "common/hash.h"
#include "common/math_util.h"
#include "common/microprofile.h"
@@ -12,71 +13,693 @@
#include "core/memory.h"
#include "video_core/debug_utils/debug_utils.h"
+#include "video_core/pica_state.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/pica_to_gl.h"
+#include "video_core/utils.h"
+#include "video_core/video_core.h"
+
+struct FormatTuple {
+ GLint internal_format;
+ GLenum format;
+ GLenum type;
+};
+
+static const std::array fb_format_tuples = {{
+ { GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 }, // RGBA8
+ { GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE }, // RGB8
+ { GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 }, // RGB5A1
+ { GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 }, // RGB565
+ { GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 }, // RGBA4
+}};
+
+static const std::array depth_format_tuples = {{
+ { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }, // D16
+ {},
+ { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }, // D24
+ { GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 }, // D24S8
+}};
+
+RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
+ transfer_framebuffers[0].Create();
+ transfer_framebuffers[1].Create();
+}
RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
- InvalidateAll();
+ FlushAll();
}
-MICROPROFILE_DEFINE(OpenGL_TextureUpload, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
+static void MortonCopyPixels(CachedSurface::PixelFormat pixel_format, u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, bool morton_to_gl) {
+ using PixelFormat = CachedSurface::PixelFormat;
-void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info) {
- const auto cached_texture = texture_cache.find(info.physical_address);
+ u8* data_ptrs[2];
+ u32 depth_stencil_shifts[2] = {24, 8};
- if (cached_texture != texture_cache.end()) {
- state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle;
- state.Apply();
- } else {
- MICROPROFILE_SCOPE(OpenGL_TextureUpload);
+ if (morton_to_gl) {
+ std::swap(depth_stencil_shifts[0], depth_stencil_shifts[1]);
+ }
- std::unique_ptr new_texture = std::make_unique();
+ if (pixel_format == PixelFormat::D24S8) {
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ const u32 coarse_y = y & ~7;
+ u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
+ u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
- new_texture->texture.Create();
- state.texture_units[texture_unit].texture_2d = new_texture->texture.handle;
- state.Apply();
- glActiveTexture(GL_TEXTURE0 + texture_unit);
+ data_ptrs[morton_to_gl] = morton_data + morton_offset;
+ data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
- u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address);
+ // Swap depth and stencil value ordering since 3DS does not match OpenGL
+ u32 depth_stencil;
+ memcpy(&depth_stencil, data_ptrs[1], sizeof(u32));
+ depth_stencil = (depth_stencil << depth_stencil_shifts[0]) | (depth_stencil >> depth_stencil_shifts[1]);
- new_texture->width = info.width;
- new_texture->height = info.height;
- new_texture->size = info.stride * info.height;
- new_texture->addr = info.physical_address;
- new_texture->hash = Common::ComputeHash64(texture_src_data, new_texture->size);
-
- std::unique_ptr[]> temp_texture_buffer_rgba(new Math::Vec4[info.width * info.height]);
-
- for (int y = 0; y < info.height; ++y) {
- for (int x = 0; x < info.width; ++x) {
- temp_texture_buffer_rgba[x + info.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, info.height - 1 - y, info);
+ memcpy(data_ptrs[0], &depth_stencil, sizeof(u32));
}
}
+ } else {
+ for (unsigned y = 0; y < height; ++y) {
+ for (unsigned x = 0; x < width; ++x) {
+ const u32 coarse_y = y & ~7;
+ u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel;
+ u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get());
+ data_ptrs[morton_to_gl] = morton_data + morton_offset;
+ data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
- texture_cache.emplace(info.physical_address, std::move(new_texture));
- }
-}
-
-void RasterizerCacheOpenGL::InvalidateInRange(PAddr addr, u32 size, bool ignore_hash) {
- // TODO: Optimize by also inserting upper bound (addr + size) of each texture into the same map and also narrow using lower_bound
- auto cache_upper_bound = texture_cache.upper_bound(addr + size);
-
- for (auto it = texture_cache.begin(); it != cache_upper_bound;) {
- const auto& info = *it->second;
-
- // Flush the texture only if the memory region intersects and a change is detected
- if (MathUtil::IntervalsIntersect(addr, size, info.addr, info.size) &&
- (ignore_hash || info.hash != Common::ComputeHash64(Memory::GetPhysicalPointer(info.addr), info.size))) {
-
- it = texture_cache.erase(it);
- } else {
- ++it;
+ memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ }
}
}
}
-void RasterizerCacheOpenGL::InvalidateAll() {
- texture_cache.clear();
+bool RasterizerCacheOpenGL::BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle& src_rect, const MathUtil::Rectangle& dst_rect) {
+ using SurfaceType = CachedSurface::SurfaceType;
+
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ // Make sure textures aren't bound to texture units, since going to bind them to framebuffer components
+ OpenGLState::ResetTexture(src_tex);
+ OpenGLState::ResetTexture(dst_tex);
+
+ // Keep track of previous framebuffer bindings
+ GLuint old_fbs[2] = { cur_state.draw.read_framebuffer, cur_state.draw.draw_framebuffer };
+ cur_state.draw.read_framebuffer = transfer_framebuffers[0].handle;
+ cur_state.draw.draw_framebuffer = transfer_framebuffers[1].handle;
+ cur_state.Apply();
+
+ u32 buffers = 0;
+
+ if (type == SurfaceType::Color || type == SurfaceType::Texture) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ buffers = GL_COLOR_BUFFER_BIT;
+ } else if (type == SurfaceType::Depth) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
+
+ buffers = GL_DEPTH_BUFFER_BIT;
+ } else if (type == SurfaceType::DepthStencil) {
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0);
+
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
+ glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0);
+
+ buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
+ }
+
+ if (OpenGLState::CheckFBStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return false;
+ }
+
+ if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
+ return false;
+ }
+
+ glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom,
+ dst_rect.left, dst_rect.top, dst_rect.right, dst_rect.bottom,
+ buffers, buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST);
+
+ // Restore previous framebuffer bindings
+ cur_state.draw.read_framebuffer = old_fbs[0];
+ cur_state.draw.draw_framebuffer = old_fbs[1];
+ cur_state.Apply();
+
+ return true;
+}
+
+bool RasterizerCacheOpenGL::TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle& dst_rect) {
+ using SurfaceType = CachedSurface::SurfaceType;
+
+ if (!CachedSurface::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) {
+ return false;
+ }
+
+ return BlitTextures(src_surface->texture.handle, dst_surface->texture.handle, CachedSurface::GetFormatType(src_surface->pixel_format), src_rect, dst_rect);
+}
+
+static void AllocateSurfaceTexture(GLuint texture, CachedSurface::PixelFormat pixel_format, u32 width, u32 height) {
+ // Allocate an uninitialized texture of appropriate size and format for the surface
+ using SurfaceType = CachedSurface::SurfaceType;
+
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ // Keep track of previous texture bindings
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+ cur_state.texture_units[0].texture_2d = texture;
+ cur_state.Apply();
+ glActiveTexture(GL_TEXTURE0);
+
+ SurfaceType type = CachedSurface::GetFormatType(pixel_format);
+
+ FormatTuple tuple;
+ if (type == SurfaceType::Color) {
+ ASSERT((size_t)pixel_format < fb_format_tuples.size());
+ tuple = fb_format_tuples[(unsigned int)pixel_format];
+ } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
+ size_t tuple_idx = (size_t)pixel_format - 14;
+ ASSERT(tuple_idx < depth_format_tuples.size());
+ tuple = depth_format_tuples[tuple_idx];
+ } else {
+ tuple = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE };
+ }
+
+ glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, width, height, 0,
+ tuple.format, tuple.type, nullptr);
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ // Restore previous texture bindings
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+}
+
+MICROPROFILE_DEFINE(OpenGL_SurfaceUpload, "OpenGL", "Surface Upload", MP_RGB(128, 64, 192));
+CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create) {
+ using PixelFormat = CachedSurface::PixelFormat;
+ using SurfaceType = CachedSurface::SurfaceType;
+
+ if (params.addr == 0) {
+ return nullptr;
+ }
+
+ u32 params_size = params.width * params.height * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
+
+ // Check for an exact match in existing surfaces
+ CachedSurface* best_exact_surface = nullptr;
+ float exact_surface_goodness = -1.f;
+
+ auto surface_interval = boost::icl::interval::right_open(params.addr, params.addr + params_size);
+ auto range = surface_cache.equal_range(surface_interval);
+ for (auto it = range.first; it != range.second; ++it) {
+ for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
+ CachedSurface* surface = it2->get();
+
+ // Check if the request matches the surface exactly
+ if (params.addr == surface->addr &&
+ params.width == surface->width && params.height == surface->height &&
+ params.pixel_format == surface->pixel_format)
+ {
+ // Make sure optional param-matching criteria are fulfilled
+ bool tiling_match = (params.is_tiled == surface->is_tiled);
+ bool res_scale_match = (params.res_scale_width == surface->res_scale_width && params.res_scale_height == surface->res_scale_height);
+ if (!match_res_scale || res_scale_match) {
+ // Prioritize same-tiling and highest resolution surfaces
+ float match_goodness = (float)tiling_match + surface->res_scale_width * surface->res_scale_height;
+ if (match_goodness > exact_surface_goodness || surface->dirty) {
+ exact_surface_goodness = match_goodness;
+ best_exact_surface = surface;
+ }
+ }
+ }
+ }
+ }
+
+ // Return the best exact surface if found
+ if (best_exact_surface != nullptr) {
+ return best_exact_surface;
+ }
+
+ // No matching surfaces found, so create a new one
+ u8* texture_src_data = Memory::GetPhysicalPointer(params.addr);
+ if (texture_src_data == nullptr) {
+ return nullptr;
+ }
+
+ MICROPROFILE_SCOPE(OpenGL_SurfaceUpload);
+
+ std::shared_ptr new_surface = std::make_shared();
+
+ new_surface->addr = params.addr;
+ new_surface->size = params_size;
+
+ new_surface->texture.Create();
+ new_surface->width = params.width;
+ new_surface->height = params.height;
+ new_surface->stride = params.stride;
+ new_surface->res_scale_width = params.res_scale_width;
+ new_surface->res_scale_height = params.res_scale_height;
+
+ new_surface->is_tiled = params.is_tiled;
+ new_surface->pixel_format = params.pixel_format;
+ new_surface->dirty = false;
+
+ if (!load_if_create) {
+ // Don't load any data; just allocate the surface's texture
+ AllocateSurfaceTexture(new_surface->texture.handle, new_surface->pixel_format, new_surface->GetScaledWidth(), new_surface->GetScaledHeight());
+ } else {
+ // TODO: Consider attempting subrect match in existing surfaces and direct blit here instead of memory upload below if that's a common scenario in some game
+
+ Memory::RasterizerFlushRegion(params.addr, params_size);
+
+ // Load data from memory to the new surface
+ OpenGLState cur_state = OpenGLState::GetCurState();
+
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+ cur_state.texture_units[0].texture_2d = new_surface->texture.handle;
+ cur_state.Apply();
+ glActiveTexture(GL_TEXTURE0);
+
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)new_surface->stride);
+ if (!new_surface->is_tiled) {
+ // TODO: Ensure this will always be a color format, not a depth or other format
+ ASSERT((size_t)new_surface->pixel_format < fb_format_tuples.size());
+ const FormatTuple& tuple = fb_format_tuples[(unsigned int)params.pixel_format];
+
+ glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0,
+ tuple.format, tuple.type, texture_src_data);
+ } else {
+ SurfaceType type = CachedSurface::GetFormatType(new_surface->pixel_format);
+ if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) {
+ FormatTuple tuple;
+ if ((size_t)params.pixel_format < fb_format_tuples.size()) {
+ tuple = fb_format_tuples[(unsigned int)params.pixel_format];
+ } else {
+ // Texture
+ tuple = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE };
+ }
+
+ std::vector> tex_buffer(params.width * params.height);
+
+ Pica::DebugUtils::TextureInfo tex_info;
+ tex_info.width = params.width;
+ tex_info.height = params.height;
+ tex_info.stride = params.width * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
+ tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format;
+ tex_info.physical_address = params.addr;
+
+ for (unsigned y = 0; y < params.height; ++y) {
+ for (unsigned x = 0; x < params.width; ++x) {
+ tex_buffer[x + params.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, params.height - 1 - y, tex_info);
+ }
+ }
+
+ glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_buffer.data());
+ } else {
+ // Depth/Stencil formats need special treatment since they aren't sampleable using LookupTexture and can't use RGBA format
+ size_t tuple_idx = (size_t)params.pixel_format - 14;
+ ASSERT(tuple_idx < depth_format_tuples.size());
+ const FormatTuple& tuple = depth_format_tuples[tuple_idx];
+
+ u32 bytes_per_pixel = CachedSurface::GetFormatBpp(params.pixel_format) / 8;
+
+ // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
+ bool use_4bpp = (params.pixel_format == PixelFormat::D24);
+
+ u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel;
+
+ std::vector temp_fb_depth_buffer(params.width * params.height * gl_bytes_per_pixel);
+
+ u8* temp_fb_depth_buffer_ptr = use_4bpp ? temp_fb_depth_buffer.data() + 1 : temp_fb_depth_buffer.data();
+
+ MortonCopyPixels(params.pixel_format, params.width, params.height, bytes_per_pixel, gl_bytes_per_pixel, texture_src_data, temp_fb_depth_buffer_ptr, true);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0,
+ tuple.format, tuple.type, temp_fb_depth_buffer.data());
+ }
+ }
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+
+ // If not 1x scale, blit 1x texture to a new scaled texture and replace texture in surface
+ if (new_surface->res_scale_width != 1.f || new_surface->res_scale_height != 1.f) {
+ OGLTexture scaled_texture;
+ scaled_texture.Create();
+
+ AllocateSurfaceTexture(scaled_texture.handle, new_surface->pixel_format, new_surface->GetScaledWidth(), new_surface->GetScaledHeight());
+ BlitTextures(new_surface->texture.handle, scaled_texture.handle, CachedSurface::GetFormatType(new_surface->pixel_format),
+ MathUtil::Rectangle(0, 0, new_surface->width, new_surface->height),
+ MathUtil::Rectangle(0, 0, new_surface->GetScaledWidth(), new_surface->GetScaledHeight()));
+
+ new_surface->texture.Release();
+ new_surface->texture.handle = scaled_texture.handle;
+ scaled_texture.handle = 0;
+ cur_state.texture_units[0].texture_2d = new_surface->texture.handle;
+ cur_state.Apply();
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+ }
+
+ Memory::RasterizerMarkRegionCached(new_surface->addr, new_surface->size, 1);
+ surface_cache.add(std::make_pair(boost::icl::interval::right_open(new_surface->addr, new_surface->addr + new_surface->size), std::set>({ new_surface })));
+ return new_surface.get();
+}
+
+CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params, bool match_res_scale, bool load_if_create, MathUtil::Rectangle& out_rect) {
+ if (params.addr == 0) {
+ return nullptr;
+ }
+
+ u32 total_pixels = params.width * params.height;
+ u32 params_size = total_pixels * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
+
+ // Attempt to find encompassing surfaces
+ CachedSurface* best_subrect_surface = nullptr;
+ float subrect_surface_goodness = -1.f;
+
+ auto surface_interval = boost::icl::interval::right_open(params.addr, params.addr + params_size);
+ auto cache_upper_bound = surface_cache.upper_bound(surface_interval);
+ for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) {
+ for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
+ CachedSurface* surface = it2->get();
+
+ // Check if the request is contained in the surface
+ if (params.addr >= surface->addr &&
+ params.addr + params_size - 1 <= surface->addr + surface->size - 1 &&
+ params.pixel_format == surface->pixel_format)
+ {
+ // Make sure optional param-matching criteria are fulfilled
+ bool tiling_match = (params.is_tiled == surface->is_tiled);
+ bool res_scale_match = (params.res_scale_width == surface->res_scale_width && params.res_scale_height == surface->res_scale_height);
+ if (!match_res_scale || res_scale_match) {
+ // Prioritize same-tiling and highest resolution surfaces
+ float match_goodness = (float)tiling_match + surface->res_scale_width * surface->res_scale_height;
+ if (match_goodness > subrect_surface_goodness || surface->dirty) {
+ subrect_surface_goodness = match_goodness;
+ best_subrect_surface = surface;
+ }
+ }
+ }
+ }
+ }
+
+ // Return the best subrect surface if found
+ if (best_subrect_surface != nullptr) {
+ unsigned int bytes_per_pixel = (CachedSurface::GetFormatBpp(best_subrect_surface->pixel_format) / 8);
+
+ int x0, y0;
+
+ if (!params.is_tiled) {
+ u32 begin_pixel_index = (params.addr - best_subrect_surface->addr) / bytes_per_pixel;
+ x0 = begin_pixel_index % best_subrect_surface->width;
+ y0 = begin_pixel_index / best_subrect_surface->width;
+
+ out_rect = MathUtil::Rectangle(x0, y0, x0 + params.width, y0 + params.height);
+ } else {
+ u32 bytes_per_tile = 8 * 8 * bytes_per_pixel;
+ u32 tiles_per_row = best_subrect_surface->width / 8;
+
+ u32 begin_tile_index = (params.addr - best_subrect_surface->addr) / bytes_per_tile;
+ x0 = begin_tile_index % tiles_per_row * 8;
+ y0 = begin_tile_index / tiles_per_row * 8;
+
+ // Tiled surfaces are flipped vertically in the rasterizer vs. 3DS memory.
+ out_rect = MathUtil::Rectangle(x0, best_subrect_surface->height - y0, x0 + params.width, best_subrect_surface->height - (y0 + params.height));
+ }
+
+ out_rect.left = (int)(out_rect.left * best_subrect_surface->res_scale_width);
+ out_rect.right = (int)(out_rect.right * best_subrect_surface->res_scale_width);
+ out_rect.top = (int)(out_rect.top * best_subrect_surface->res_scale_height);
+ out_rect.bottom = (int)(out_rect.bottom * best_subrect_surface->res_scale_height);
+
+ return best_subrect_surface;
+ }
+
+ // No subrect found - create and return a new surface
+ if (!params.is_tiled) {
+ out_rect = MathUtil::Rectangle(0, 0, (int)(params.width * params.res_scale_width), (int)(params.height * params.res_scale_height));
+ } else {
+ out_rect = MathUtil::Rectangle(0, (int)(params.height * params.res_scale_height), (int)(params.width * params.res_scale_width), 0);
+ }
+
+ return GetSurface(params, match_res_scale, load_if_create);
+}
+
+CachedSurface* RasterizerCacheOpenGL::GetTextureSurface(const Pica::Regs::FullTextureConfig& config) {
+ Pica::DebugUtils::TextureInfo info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format);
+
+ CachedSurface params;
+ params.addr = info.physical_address;
+ params.width = info.width;
+ params.height = info.height;
+ params.is_tiled = true;
+ params.pixel_format = CachedSurface::PixelFormatFromTextureFormat(info.format);
+ return GetSurface(params, false, true);
+}
+
+std::tuple> RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config) {
+ const auto& regs = Pica::g_state.regs;
+
+ // Make sur that framebuffers don't overlap if both color and depth are being used
+ u32 fb_area = config.GetWidth() * config.GetHeight();
+ bool framebuffers_overlap = config.GetColorBufferPhysicalAddress() != 0 &&
+ config.GetDepthBufferPhysicalAddress() != 0 &&
+ MathUtil::IntervalsIntersect(config.GetColorBufferPhysicalAddress(), fb_area * GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(config.color_format.Value())),
+ config.GetDepthBufferPhysicalAddress(), fb_area * Pica::Regs::BytesPerDepthPixel(config.depth_format));
+ bool using_color_fb = config.GetColorBufferPhysicalAddress() != 0;
+ bool using_depth_fb = config.GetDepthBufferPhysicalAddress() != 0 && (regs.output_merger.depth_test_enable || regs.output_merger.depth_write_enable || !framebuffers_overlap);
+
+ if (framebuffers_overlap && using_color_fb && using_depth_fb) {
+ LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; overlapping framebuffers not supported!");
+ using_depth_fb = false;
+ }
+
+ // get color and depth surfaces
+ CachedSurface color_params;
+ CachedSurface depth_params;
+ color_params.width = depth_params.width = config.GetWidth();
+ color_params.height = depth_params.height = config.GetHeight();
+ color_params.is_tiled = depth_params.is_tiled = true;
+ if (VideoCore::g_scaled_resolution_enabled) {
+ auto layout = VideoCore::g_emu_window->GetFramebufferLayout();
+
+ // Assume same scaling factor for top and bottom screens
+ color_params.res_scale_width = depth_params.res_scale_width = (float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth;
+ color_params.res_scale_height = depth_params.res_scale_height = (float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight;
+ }
+
+ color_params.addr = config.GetColorBufferPhysicalAddress();
+ color_params.pixel_format = CachedSurface::PixelFormatFromColorFormat(config.color_format);
+
+ depth_params.addr = config.GetDepthBufferPhysicalAddress();
+ depth_params.pixel_format = CachedSurface::PixelFormatFromDepthFormat(config.depth_format);
+
+ MathUtil::Rectangle color_rect;
+ CachedSurface* color_surface = using_color_fb ? GetSurfaceRect(color_params, true, true, color_rect) : nullptr;
+
+ MathUtil::Rectangle depth_rect;
+ CachedSurface* depth_surface = using_depth_fb ? GetSurfaceRect(depth_params, true, true, depth_rect) : nullptr;
+
+ // Sanity check to make sure found surfaces aren't the same
+ if (using_depth_fb && using_color_fb && color_surface == depth_surface) {
+ LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer surfaces overlap; overlapping surfaces not supported!");
+ using_depth_fb = false;
+ depth_surface = nullptr;
+ }
+
+ MathUtil::Rectangle rect;
+
+ if (color_surface != nullptr && depth_surface != nullptr && (depth_rect.left != color_rect.left || depth_rect.top != color_rect.top)) {
+ // Can't specify separate color and depth viewport offsets in OpenGL, so re-zero both if they don't match
+ if (color_rect.left != 0 || color_rect.top != 0) {
+ color_surface = GetSurface(color_params, true, true);
+ }
+
+ if (depth_rect.left != 0 || depth_rect.top != 0) {
+ depth_surface = GetSurface(depth_params, true, true);
+ }
+
+ if (!color_surface->is_tiled) {
+ rect = MathUtil::Rectangle(0, 0, (int)(color_params.width * color_params.res_scale_width), (int)(color_params.height * color_params.res_scale_height));
+ } else {
+ rect = MathUtil::Rectangle(0, (int)(color_params.height * color_params.res_scale_height), (int)(color_params.width * color_params.res_scale_width), 0);
+ }
+ } else if (color_surface != nullptr) {
+ rect = color_rect;
+ } else if (depth_surface != nullptr) {
+ rect = depth_rect;
+ } else {
+ rect = MathUtil::Rectangle(0, 0, 0, 0);
+ }
+
+ return std::make_tuple(color_surface, depth_surface, rect);
+}
+
+CachedSurface* RasterizerCacheOpenGL::TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config) {
+ auto surface_interval = boost::icl::interval::right_open(config.GetStartAddress(), config.GetEndAddress());
+ auto range = surface_cache.equal_range(surface_interval);
+ for (auto it = range.first; it != range.second; ++it) {
+ for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
+ int bits_per_value = 0;
+ if (config.fill_24bit) {
+ bits_per_value = 24;
+ } else if (config.fill_32bit) {
+ bits_per_value = 32;
+ } else {
+ bits_per_value = 16;
+ }
+
+ CachedSurface* surface = it2->get();
+
+ if (surface->addr == config.GetStartAddress() &&
+ CachedSurface::GetFormatBpp(surface->pixel_format) == bits_per_value &&
+ (surface->width * surface->height * CachedSurface::GetFormatBpp(surface->pixel_format) / 8) == (config.GetEndAddress() - config.GetStartAddress()))
+ {
+ return surface;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+MICROPROFILE_DEFINE(OpenGL_SurfaceDownload, "OpenGL", "Surface Download", MP_RGB(128, 192, 64));
+void RasterizerCacheOpenGL::FlushSurface(CachedSurface* surface) {
+ using PixelFormat = CachedSurface::PixelFormat;
+ using SurfaceType = CachedSurface::SurfaceType;
+
+ if (!surface->dirty) {
+ return;
+ }
+
+ MICROPROFILE_SCOPE(OpenGL_SurfaceDownload);
+
+ u8* dst_buffer = Memory::GetPhysicalPointer(surface->addr);
+ if (dst_buffer == nullptr) {
+ return;
+ }
+
+ OpenGLState cur_state = OpenGLState::GetCurState();
+ GLuint old_tex = cur_state.texture_units[0].texture_2d;
+
+ OGLTexture unscaled_tex;
+ GLuint texture_to_flush = surface->texture.handle;
+
+ // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush
+ if (surface->res_scale_width != 1.f || surface->res_scale_height != 1.f) {
+ unscaled_tex.Create();
+
+ AllocateSurfaceTexture(unscaled_tex.handle, surface->pixel_format, surface->width, surface->height);
+ BlitTextures(surface->texture.handle, unscaled_tex.handle, CachedSurface::GetFormatType(surface->pixel_format),
+ MathUtil::Rectangle(0, 0, surface->GetScaledWidth(), surface->GetScaledHeight()),
+ MathUtil::Rectangle(0, 0, surface->width, surface->height));
+
+ texture_to_flush = unscaled_tex.handle;
+ }
+
+ cur_state.texture_units[0].texture_2d = texture_to_flush;
+ cur_state.Apply();
+ glActiveTexture(GL_TEXTURE0);
+
+ glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)surface->stride);
+ if (!surface->is_tiled) {
+ // TODO: Ensure this will always be a color format, not a depth or other format
+ ASSERT((size_t)surface->pixel_format < fb_format_tuples.size());
+ const FormatTuple& tuple = fb_format_tuples[(unsigned int)surface->pixel_format];
+
+ glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, dst_buffer);
+ } else {
+ SurfaceType type = CachedSurface::GetFormatType(surface->pixel_format);
+ if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) {
+ ASSERT((size_t)surface->pixel_format < fb_format_tuples.size());
+ const FormatTuple& tuple = fb_format_tuples[(unsigned int)surface->pixel_format];
+
+ u32 bytes_per_pixel = CachedSurface::GetFormatBpp(surface->pixel_format) / 8;
+
+ std::vector temp_gl_buffer(surface->width * surface->height * bytes_per_pixel);
+
+ glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, temp_gl_buffer.data());
+
+ // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary.
+ MortonCopyPixels(surface->pixel_format, surface->width, surface->height, bytes_per_pixel, bytes_per_pixel, dst_buffer, temp_gl_buffer.data(), false);
+ } else {
+ // Depth/Stencil formats need special treatment since they aren't sampleable using LookupTexture and can't use RGBA format
+ size_t tuple_idx = (size_t)surface->pixel_format - 14;
+ ASSERT(tuple_idx < depth_format_tuples.size());
+ const FormatTuple& tuple = depth_format_tuples[tuple_idx];
+
+ u32 bytes_per_pixel = CachedSurface::GetFormatBpp(surface->pixel_format) / 8;
+
+ // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
+ bool use_4bpp = (surface->pixel_format == PixelFormat::D24);
+
+ u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel;
+
+ std::vector temp_gl_buffer(surface->width * surface->height * gl_bytes_per_pixel);
+
+ glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, temp_gl_buffer.data());
+
+ u8* temp_gl_buffer_ptr = use_4bpp ? temp_gl_buffer.data() + 1 : temp_gl_buffer.data();
+
+ MortonCopyPixels(surface->pixel_format, surface->width, surface->height, bytes_per_pixel, gl_bytes_per_pixel, dst_buffer, temp_gl_buffer_ptr, false);
+ }
+ }
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+
+ surface->dirty = false;
+
+ cur_state.texture_units[0].texture_2d = old_tex;
+ cur_state.Apply();
+}
+
+void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, bool invalidate) {
+ if (size == 0) {
+ return;
+ }
+
+ // Gather up unique surfaces that touch the region
+ std::unordered_set> touching_surfaces;
+
+ auto surface_interval = boost::icl::interval::right_open(addr, addr + size);
+ auto cache_upper_bound = surface_cache.upper_bound(surface_interval);
+ for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) {
+ std::copy_if(it->second.begin(), it->second.end(), std::inserter(touching_surfaces, touching_surfaces.end()),
+ [skip_surface](std::shared_ptr surface) { return (surface.get() != skip_surface); });
+ }
+
+ // Flush and invalidate surfaces
+ for (auto surface : touching_surfaces) {
+ FlushSurface(surface.get());
+ if (invalidate) {
+ Memory::RasterizerMarkRegionCached(surface->addr, surface->size, -1);
+ surface_cache.subtract(std::make_pair(boost::icl::interval::right_open(surface->addr, surface->addr + surface->size), std::set>({ surface })));
+ }
+ }
+}
+
+void RasterizerCacheOpenGL::FlushAll() {
+ for (auto& surfaces : surface_cache) {
+ for (auto& surface : surfaces.second) {
+ FlushSurface(surface.get());
+ }
+ }
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index b69651427..893d51138 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -6,38 +6,211 @@
#include