diff options
| -rw-r--r-- | src/core/hle/service/fs/archive.cpp | 1 | ||||
| -rw-r--r-- | src/core/hle/service/gsp_gpu.cpp | 31 | ||||
| -rw-r--r-- | src/core/hle/service/y2r_u.cpp | 10 | ||||
| -rw-r--r-- | src/core/hw/gpu.cpp | 327 | ||||
| -rw-r--r-- | src/core/hw/gpu.h | 4 | ||||
| -rw-r--r-- | src/core/memory.cpp | 140 | ||||
| -rw-r--r-- | src/core/memory.h | 16 | ||||
| -rw-r--r-- | src/video_core/debug_utils/debug_utils.cpp | 4 | ||||
| -rw-r--r-- | src/video_core/pica.h | 2 | ||||
| -rw-r--r-- | src/video_core/rasterizer_interface.h | 31 | ||||
| -rw-r--r-- | src/video_core/renderer_base.cpp | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 839 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.h | 76 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 699 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.h | 209 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_state.cpp | 63 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_state.h | 27 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/renderer_opengl.cpp | 128 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/renderer_opengl.h | 44 | ||||
| -rw-r--r-- | src/video_core/swrasterizer.h | 6 | 
20 files changed, 1719 insertions, 940 deletions
| 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<bool> File::SyncRequest() {                  return read.Code();              }              cmd_buff[2] = static_cast<u32>(*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 (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); - -                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 }); +            if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) { +                u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); +                u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); -                    std::memcpy(dst_pointer, src_pointer, copy_size); -                    src_pointer += copy_size; -                    dst_pointer += copy_size; +                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; -                    remaining_input -= copy_size; -                    remaining_output -= copy_size; -                    remaining_size -= copy_size; +                    size_t contiguous_input_size = config.texture_copy.size / input_width * (input_width + input_gap); +                    Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), contiguous_input_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; -                    } -                } +                    size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); +                    Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_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); +                    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); -                VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size); +                        std::memcpy(dst_pointer, src_pointer, copy_size); +                        src_pointer += copy_size; +                        dst_pointer += copy_size; -                GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); -                break; -            } +                        remaining_input -= copy_size; +                        remaining_output -= copy_size; +                        remaining_size -= copy_size; -            if (config.scaling > config.ScaleXY) { -                LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value()); -                UNIMPLEMENTED(); -                break; -            } +                        if (remaining_input == 0) { +                            remaining_input = input_width; +                            src_pointer += input_gap; +                        } +                        if (remaining_output == 0) { +                            remaining_output = output_width; +                            dst_pointer += output_gap; +                        } +                    } -            if (config.input_linear && config.scaling != config.NoScale) { -                LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input"); -                UNIMPLEMENTED(); -                break; -            } +                    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); -            bool horizontal_scale = config.scaling != config.NoScale; -            bool vertical_scale = config.scaling == config.ScaleXY; +                    GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); +                    break; +                } -            u32 output_width = config.output_width >> horizontal_scale; -            u32 output_height = config.output_height >> vertical_scale; +                if (config.scaling > config.ScaleXY) { +                    LOG_CRITICAL(HW_GPU, "Unimplemented display transfer scaling mode %u", config.scaling.Value()); +                    UNIMPLEMENTED(); +                    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); +                if (config.input_linear && config.scaling != config.NoScale) { +                    LOG_CRITICAL(HW_GPU, "Scaling is only implemented on tiled input"); +                    UNIMPLEMENTED(); +                    break; +                } -            VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), input_size); +                int horizontal_scale = config.scaling != config.NoScale ? 1 : 0; +                int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0; -            for (u32 y = 0; y < output_height; ++y) { -                for (u32 x = 0; x < output_width; ++x) { -                    Math::Vec4<u8> src_color; +                u32 output_width = config.output_width >> horizontal_scale; +                u32 output_height = config.output_height >> vertical_scale; -                    // 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; +                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); -                    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; -                    } +                Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size); +                Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size); -                    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; +                for (u32 y = 0; y < output_height; ++y) { +                    for (u32 x = 0; x < output_width; ++x) { +                        Math::Vec4<u8> src_color; -                    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; +                        // 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; -                            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 (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;                          } -                    } 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; +                        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 { -                            // 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 (!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<u8> pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel); -                        src_color = ((src_color + pixel) / 2).Cast<u8>(); -                    } else if (config.scaling == config.ScaleXY) { -                        Math::Vec4<u8> pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel); -                        Math::Vec4<u8> pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel); -                        Math::Vec4<u8> pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel); -                        src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast<u8>(); -                    } +                        const u8* src_pixel = src_pointer + src_offset; +                        src_color = DecodePixel(config.input_format, src_pixel); +                        if (config.scaling == config.ScaleX) { +                            Math::Vec4<u8> pixel = DecodePixel(config.input_format, src_pixel + src_bytes_per_pixel); +                            src_color = ((src_color + pixel) / 2).Cast<u8>(); +                        } else if (config.scaling == config.ScaleXY) { +                            Math::Vec4<u8> pixel1 = DecodePixel(config.input_format, src_pixel + 1 * src_bytes_per_pixel); +                            Math::Vec4<u8> pixel2 = DecodePixel(config.input_format, src_pixel + 2 * src_bytes_per_pixel); +                            Math::Vec4<u8> pixel3 = DecodePixel(config.input_format, src_pixel + 3 * src_bytes_per_pixel); +                            src_color = (((src_color + pixel1) + (pixel2 + pixel3)) / 4).Cast<u8>(); +                        } -                    u8* dst_pixel = dst_pointer + dst_offset; -                    switch (config.output_format) { -                    case Regs::PixelFormat::RGBA8: -                        Color::EncodeRGBA8(src_color, dst_pixel); -                        break; +                        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::RGB8: +                            Color::EncodeRGB8(src_color, dst_pixel); +                            break; -                    case Regs::PixelFormat::RGB565: -                        Color::EncodeRGB565(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::RGB5A1: +                            Color::EncodeRGB5A1(src_color, dst_pixel); +                            break; -                    case Regs::PixelFormat::RGBA4: -                        Color::EncodeRGBA4(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; +                        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<PageType, NUM_ENTRIES> attributes; + +    /** +     * Indicates the number of externally cached resources touching a page that should be +     * flushed before the memory is accessed +     */ +    std::array<u8, NUM_ENTRIES> 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) { @@ -107,6 +128,28 @@ void UnmapRegion(VAddr base, u32 size) {  }  /** + * 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`.   */  static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { @@ -126,6 +169,7 @@ template <typename T>  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<T>(GetMMIOHandler(vaddr), vaddr); +    case PageType::RasterizerCachedSpecial: +    { +        RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + +        return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); +    }      default:          UNREACHABLE();      } @@ -153,6 +211,7 @@ template <typename T>  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<T>(GetMMIOHandler(vaddr), vaddr, data);          break; +    case PageType::RasterizerCachedSpecial: +    { +        RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + +        WriteMMIO<T>(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<u8>(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/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<std::mutex> 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<VideoCore::SWRasterizer>();          } -        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; + +    // Sync and bind the framebuffer surfaces +    CachedSurface* color_surface; +    CachedSurface* depth_surface; +    MathUtil::Rectangle<int> 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; +    } + +    // 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; +        } +    } -    if (state.draw.shader_dirty) { +    // Sync and bind the shader +    if (shader_dirty) {          SetShader(); -        state.draw.shader_dirty = false; +        shader_dirty = false;      } -    for (unsigned index = 0; index < lighting_lut.size(); index++) { +    // 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()); -    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; +    // 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); +    } -    res_cache.InvalidateInRange(cached_fb_color_addr, cached_fb_color_size, true); -    res_cache.InvalidateInRange(cached_fb_depth_addr, cached_fb_depth_size, true); -} +    vertex_batch.clear(); -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::FlushAll() { +    res_cache.FlushAll(); +} +  void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { -    const auto& regs = Pica::g_state.regs; +    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 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 (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)) -        CommitDepthBuffer(); +    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); + +    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<int> 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<int> 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;  } -void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) { -    const auto& regs = Pica::g_state.regs; +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); -    u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) -                               * fb_color_texture.width * fb_color_texture.height; +    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(); -    u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) -                               * fb_depth_texture.width * fb_depth_texture.height; +    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 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 (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<u8> 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); +    } -    if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) -        ReloadDepthBuffer(); +    cur_state.draw.draw_framebuffer = old_fb; +    // TODO: Return scissor test to previous value when scissor test is implemented +    cur_state.Apply(); -    // Notify cache of flush in case the region touches a cached resource -    res_cache.InvalidateInRange(addr, size); +    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<int> 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>((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<PicaShader> shader = std::make_unique<PicaShader>(); @@ -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<GLsizei>(regs.framebuffer.GetWidth()) || -                           fb_color_texture.height != static_cast<GLsizei>(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<u8[]> 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<u8[]> 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<u8[]> 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<u8[]> 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<HardwareVertex> vertex_batch; -    OpenGLState state; - -    PAddr cached_fb_color_addr; -    PAddr cached_fb_depth_addr; - -    // Hardware rasterizer -    std::array<SamplerInfo, 3> texture_samplers; -    TextureInfo fb_color_texture; -    DepthTextureInfo fb_depth_texture; -      std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> 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<SamplerInfo, 3> texture_samplers;      OGLVertexArray vertex_array;      OGLBuffer vertex_buffer;      OGLBuffer uniform_buffer;      OGLFramebuffer framebuffer; -    std::array<OGLTexture, 6> lighting_lut; +    std::array<OGLTexture, 6> lighting_luts;      std::array<std::array<GLvec4, 256>, 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 <memory> +#include <unordered_set> +#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<FormatTuple, 5> 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<FormatTuple, 4> 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; + +    u8* data_ptrs[2]; +    u32 depth_stencil_shifts[2] = {24, 8}; -void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info) { -    const auto cached_texture = texture_cache.find(info.physical_address); +    if (morton_to_gl) { +        std::swap(depth_stencil_shifts[0], depth_stencil_shifts[1]); +    } + +    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; + +                data_ptrs[morton_to_gl] = morton_data + morton_offset; +                data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; -    if (cached_texture != texture_cache.end()) { -        state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle; -        state.Apply(); +                // 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]); + +                memcpy(data_ptrs[0], &depth_stencil, sizeof(u32)); +            } +        }      } else { -        MICROPROFILE_SCOPE(OpenGL_TextureUpload); +        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; + +                data_ptrs[morton_to_gl] = morton_data + morton_offset; +                data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + +                memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); +            } +        } +    } +} + +bool RasterizerCacheOpenGL::BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& 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<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& 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<PAddr>::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<CachedSurface> new_surface = std::make_shared<CachedSurface>(); + +    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<Math::Vec4<u8>> 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<u8> 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); -        std::unique_ptr<CachedTexture> new_texture = std::make_unique<CachedTexture>(); +        // 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(); -        new_texture->texture.Create(); -        state.texture_units[texture_unit].texture_2d = new_texture->texture.handle; -        state.Apply(); -        glActiveTexture(GL_TEXTURE0 + texture_unit); +            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<int>(0, 0, new_surface->width, new_surface->height), +                MathUtil::Rectangle<int>(0, 0, new_surface->GetScaledWidth(), new_surface->GetScaledHeight())); -        u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address); +            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<PAddr>::right_open(new_surface->addr, new_surface->addr + new_surface->size), std::set<std::shared_ptr<CachedSurface>>({ new_surface }))); +    return new_surface.get(); +} -        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); +CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params, bool match_res_scale, bool load_if_create, MathUtil::Rectangle<int>& 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; -        std::unique_ptr<Math::Vec4<u8>[]> temp_texture_buffer_rgba(new Math::Vec4<u8>[info.width * info.height]); +    auto surface_interval = boost::icl::interval<PAddr>::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(); -        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); +            // 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<int>(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<int>(x0, best_subrect_surface->height - y0, x0 + params.width, best_subrect_surface->height - (y0 + params.height)); +        } -        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get()); +        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); -        texture_cache.emplace(info.physical_address, std::move(new_texture)); +        return best_subrect_surface;      } + +    // No subrect found - create and return a new surface +    if (!params.is_tiled) { +        out_rect = MathUtil::Rectangle<int>(0, 0, (int)(params.width * params.res_scale_width), (int)(params.height * params.res_scale_height)); +    } else { +        out_rect = MathUtil::Rectangle<int>(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);  } -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); +std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> 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<int> color_rect; +    CachedSurface* color_surface = using_color_fb ? GetSurfaceRect(color_params, true, true, color_rect) : nullptr; + +    MathUtil::Rectangle<int> 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<int> rect; -    for (auto it = texture_cache.begin(); it != cache_upper_bound;) { -        const auto& info = *it->second; +    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); +        } -        // 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))) { +        if (depth_rect.left != 0 || depth_rect.top != 0) { +            depth_surface = GetSurface(depth_params, true, true); +        } -            it = texture_cache.erase(it); +        if (!color_surface->is_tiled) { +            rect = MathUtil::Rectangle<int>(0, 0, (int)(color_params.width * color_params.res_scale_width), (int)(color_params.height * color_params.res_scale_height));          } else { -            ++it; +            rect = MathUtil::Rectangle<int>(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<int>(0, 0, 0, 0);      } + +    return std::make_tuple(color_surface, depth_surface, rect);  } -void RasterizerCacheOpenGL::InvalidateAll() { -    texture_cache.clear(); +CachedSurface* RasterizerCacheOpenGL::TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config) { +    auto surface_interval = boost::icl::interval<PAddr>::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<int>(0, 0, surface->GetScaledWidth(), surface->GetScaledHeight()), +            MathUtil::Rectangle<int>(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<u8> 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<u8> 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<std::shared_ptr<CachedSurface>> touching_surfaces; + +    auto surface_interval = boost::icl::interval<PAddr>::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<CachedSurface> 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<PAddr>::right_open(surface->addr, surface->addr + surface->size), std::set<std::shared_ptr<CachedSurface>>({ 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 <map>  #include <memory> +#include <set> + +#include <boost/icl/interval_map.hpp> + +#include "common/math_util.h" + +#include "core/hw/gpu.h"  #include "video_core/pica.h"  #include "video_core/debug_utils/debug_utils.h"  #include "video_core/renderer_opengl/gl_resource_manager.h"  #include "video_core/renderer_opengl/gl_state.h" +struct CachedSurface; + +using SurfaceCache = boost::icl::interval_map<PAddr, std::set<std::shared_ptr<CachedSurface>>>; + +struct CachedSurface { +    enum class PixelFormat { +        // First 5 formats are shared between textures and color buffers +        RGBA8        =  0, +        RGB8         =  1, +        RGB5A1       =  2, +        RGB565       =  3, +        RGBA4        =  4, + +        // Texture-only formats +        IA8          =  5, +        RG8          =  6, +        I8           =  7, +        A8           =  8, +        IA4          =  9, +        I4           = 10, +        A4           = 11, +        ETC1         = 12, +        ETC1A4       = 13, + +        // Depth buffer-only formats +        D16          = 14, +        // gap +        D24          = 16, +        D24S8        = 17, + +        Invalid      = 255, +    }; + +    enum class SurfaceType { +        Color        = 0, +        Texture      = 1, +        Depth        = 2, +        DepthStencil = 3, +        Invalid      = 4, +    }; + +    static unsigned int GetFormatBpp(CachedSurface::PixelFormat format) { +        static const std::array<unsigned int, 18> bpp_table = { +            32, // RGBA8 +            24, // RGB8 +            16, // RGB5A1 +            16, // RGB565 +            16, // RGBA4 +            16, // IA8 +            16, // RG8 +            8,  // I8 +            8,  // A8 +            8,  // IA4 +            4,  // I4 +            4,  // A4 +            4,  // ETC1 +            8,  // ETC1A4 +            16, // D16 +            0, +            24, // D24 +            32, // D24S8 +        }; + +        ASSERT((unsigned int)format < ARRAY_SIZE(bpp_table)); +        return bpp_table[(unsigned int)format]; +    } + +    static PixelFormat PixelFormatFromTextureFormat(Pica::Regs::TextureFormat format) { +        return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; +    } + +    static PixelFormat PixelFormatFromColorFormat(Pica::Regs::ColorFormat format) { +        return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; +    } + +    static PixelFormat PixelFormatFromDepthFormat(Pica::Regs::DepthFormat format) { +        return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14) : PixelFormat::Invalid; +    } + +    static PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) { +        switch (format) { +        // RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat +        case GPU::Regs::PixelFormat::RGB565: +            return PixelFormat::RGB565; +        case GPU::Regs::PixelFormat::RGB5A1: +            return PixelFormat::RGB5A1; +        default: +            return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; +        } +    } + +    static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { +        SurfaceType a_type = GetFormatType(pixel_format_a); +        SurfaceType b_type = GetFormatType(pixel_format_b); + +        if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) && (b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) { +            return true; +        } + +        if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) { +            return true; +        } + +        if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) { +            return true; +        } + +        return false; +    } + +    static SurfaceType GetFormatType(PixelFormat pixel_format) { +        if ((unsigned int)pixel_format < 5) { +            return SurfaceType::Color; +        } + +        if ((unsigned int)pixel_format < 14) { +            return SurfaceType::Texture; +        } + +        if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) { +            return SurfaceType::Depth; +        } + +        if (pixel_format == PixelFormat::D24S8) { +            return SurfaceType::DepthStencil; +        } + +        return SurfaceType::Invalid; +    } + +    u32 GetScaledWidth() const { +        return (u32)(width * res_scale_width); +    } + +    u32 GetScaledHeight() const { +        return (u32)(height * res_scale_height); +    } + +    PAddr addr; +    u32 size; + +    PAddr min_valid; +    PAddr max_valid; + +    OGLTexture texture; +    u32 width; +    u32 height; +    u32 stride = 0; +    float res_scale_width = 1.f; +    float res_scale_height = 1.f; + +    bool is_tiled; +    PixelFormat pixel_format; +    bool dirty; +}; +  class RasterizerCacheOpenGL : NonCopyable {  public: +    RasterizerCacheOpenGL();      ~RasterizerCacheOpenGL(); +    /// Blits one texture to another +    bool BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& dst_rect); + +    /// Attempt to blit one surface's texture to another +    bool TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect); +      /// Loads a texture from 3DS memory to OpenGL and caches it (if not already cached) -    void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info); +    CachedSurface* GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create); -    void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::Regs::FullTextureConfig& config) { -        LoadAndBindTexture(state, texture_unit, Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format)); -    } +    /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from 3DS memory to OpenGL and caches it (if not already cached) +    CachedSurface* GetSurfaceRect(const CachedSurface& params, bool match_res_scale, bool load_if_create, MathUtil::Rectangle<int>& out_rect); -    /// Invalidate any cached resource intersecting the specified region. -    void InvalidateInRange(PAddr addr, u32 size, bool ignore_hash = false); +    /// Gets a surface based on the texture configuration +    CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config); -    /// Invalidate all cached OpenGL resources tracked by this cache manager -    void InvalidateAll(); +    /// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer configuration +    std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config); -private: -    struct CachedTexture { -        OGLTexture texture; -        GLuint width; -        GLuint height; -        u32 size; -        u64 hash; -        PAddr addr; -    }; +    /// Attempt to get a surface that exactly matches the fill region and format +    CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config); + +    /// Write the surface back to memory +    void FlushSurface(CachedSurface* surface); -    std::map<PAddr, std::unique_ptr<CachedTexture>> texture_cache; +    /// Write any cached resources overlapping the region back to memory (if dirty) and optionally invalidate them in the cache +    void FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, bool invalidate); + +    /// Flush all cached resources tracked by this cache manager +    void FlushAll(); + +private: +    SurfaceCache surface_cache; +    OGLFramebuffer transfer_framebuffers[2];  }; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 08e4d0b54..f04bdd8c5 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -3,6 +3,7 @@  // Refer to the license.txt file included.  #include "video_core/pica.h" +#include "video_core/renderer_opengl/gl_resource_manager.h"  #include "video_core/renderer_opengl/gl_state.h"  OpenGLState OpenGLState::cur_state; @@ -48,17 +49,19 @@ OpenGLState::OpenGLState() {          texture_unit.sampler = 0;      } -    for (auto& lut : lighting_lut) { +    for (auto& lut : lighting_luts) {          lut.texture_1d = 0;      } -    draw.framebuffer = 0; +    draw.read_framebuffer = 0; +    draw.draw_framebuffer = 0;      draw.vertex_array = 0;      draw.vertex_buffer = 0; +    draw.uniform_buffer = 0;      draw.shader_program = 0;  } -void OpenGLState::Apply() { +void OpenGLState::Apply() const {      // Culling      if (cull.enabled != cur_state.cull.enabled) {          if (cull.enabled) { @@ -175,16 +178,19 @@ void OpenGLState::Apply() {      }      // Lighting LUTs -    for (unsigned i = 0; i < ARRAY_SIZE(lighting_lut); ++i) { -        if (lighting_lut[i].texture_1d != cur_state.lighting_lut[i].texture_1d) { +    for (unsigned i = 0; i < ARRAY_SIZE(lighting_luts); ++i) { +        if (lighting_luts[i].texture_1d != cur_state.lighting_luts[i].texture_1d) {              glActiveTexture(GL_TEXTURE3 + i); -            glBindTexture(GL_TEXTURE_1D, lighting_lut[i].texture_1d); +            glBindTexture(GL_TEXTURE_1D, lighting_luts[i].texture_1d);          }      }      // Framebuffer -    if (draw.framebuffer != cur_state.draw.framebuffer) { -        glBindFramebuffer(GL_FRAMEBUFFER, draw.framebuffer); +    if (draw.read_framebuffer != cur_state.draw.read_framebuffer) { +        glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer); +    } +    if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) { +        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer);      }      // Vertex array @@ -210,45 +216,58 @@ void OpenGLState::Apply() {      cur_state = *this;  } -void OpenGLState::ResetTexture(GLuint id) { +GLenum OpenGLState::CheckFBStatus(GLenum target) { +    GLenum fb_status = glCheckFramebufferStatus(target); +    if (fb_status != GL_FRAMEBUFFER_COMPLETE) { +        const char* fb_description = (target == GL_READ_FRAMEBUFFER ? "READ" : (target == GL_DRAW_FRAMEBUFFER ? "DRAW" : "UNK")); +        LOG_CRITICAL(Render_OpenGL, "OpenGL %s framebuffer check failed, status %X", fb_description, fb_status); +    } + +    return fb_status; +} + +void OpenGLState::ResetTexture(GLuint handle) {      for (auto& unit : cur_state.texture_units) { -        if (unit.texture_2d == id) { +        if (unit.texture_2d == handle) {              unit.texture_2d = 0;          }      }  } -void OpenGLState::ResetSampler(GLuint id) { +void OpenGLState::ResetSampler(GLuint handle) {      for (auto& unit : cur_state.texture_units) { -        if (unit.sampler == id) { +        if (unit.sampler == handle) {              unit.sampler = 0;          }      }  } -void OpenGLState::ResetProgram(GLuint id) { -    if (cur_state.draw.shader_program == id) { +void OpenGLState::ResetProgram(GLuint handle) { +    if (cur_state.draw.shader_program == handle) {          cur_state.draw.shader_program = 0;      }  } -void OpenGLState::ResetBuffer(GLuint id) { -    if (cur_state.draw.vertex_buffer == id) { +void OpenGLState::ResetBuffer(GLuint handle) { +    if (cur_state.draw.vertex_buffer == handle) {          cur_state.draw.vertex_buffer = 0;      } -    if (cur_state.draw.uniform_buffer == id) { +    if (cur_state.draw.uniform_buffer == handle) {          cur_state.draw.uniform_buffer = 0;      }  } -void OpenGLState::ResetVertexArray(GLuint id) { -    if (cur_state.draw.vertex_array == id) { +void OpenGLState::ResetVertexArray(GLuint handle) { +    if (cur_state.draw.vertex_array == handle) {          cur_state.draw.vertex_array = 0;      }  } -void OpenGLState::ResetFramebuffer(GLuint id) { -    if (cur_state.draw.framebuffer == id) { -        cur_state.draw.framebuffer = 0; +void OpenGLState::ResetFramebuffer(GLuint handle) { +    if (cur_state.draw.read_framebuffer == handle) { +        cur_state.draw.read_framebuffer = 0; +    } +    if (cur_state.draw.draw_framebuffer == handle) { +        cur_state.draw.draw_framebuffer = 0;      }  } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index e848058d7..0f72e9004 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -5,6 +5,7 @@  #pragma once  #include <glad/glad.h> +#include <memory>  class OpenGLState {  public: @@ -63,15 +64,15 @@ public:      struct {          GLuint texture_1d; // GL_TEXTURE_BINDING_1D -    } lighting_lut[6]; +    } lighting_luts[6];      struct { -        GLuint framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING +        GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING +        GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING          GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING          GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING          GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING          GLuint shader_program; // GL_CURRENT_PROGRAM -        bool shader_dirty;      } draw;      OpenGLState(); @@ -82,14 +83,18 @@ public:      }      /// Apply this state as the current OpenGL state -    void Apply(); - -    static void ResetTexture(GLuint id); -    static void ResetSampler(GLuint id); -    static void ResetProgram(GLuint id); -    static void ResetBuffer(GLuint id); -    static void ResetVertexArray(GLuint id); -    static void ResetFramebuffer(GLuint id); +    void Apply() const; + +    /// Check the status of the current OpenGL read or draw framebuffer configuration +    static GLenum CheckFBStatus(GLenum target); + +    /// Resets and unbinds any references to the given resource in the current OpenGL state +    static void ResetTexture(GLuint handle); +    static void ResetSampler(GLuint handle); +    static void ResetProgram(GLuint handle); +    static void ResetBuffer(GLuint handle); +    static void ResetVertexArray(GLuint handle); +    static void ResetFramebuffer(GLuint handle);  private:      static OpenGLState cur_state; diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 11c4d0daf..8f907593f 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -107,7 +107,7 @@ void RendererOpenGL::SwapBuffers() {      OpenGLState prev_state = OpenGLState::GetCurState();      state.Apply(); -    for(int i : {0, 1}) { +    for (int i : {0, 1}) {          const auto& framebuffer = GPU::g_regs.framebuffer_config[i];          // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04 @@ -117,25 +117,25 @@ void RendererOpenGL::SwapBuffers() {          LCD::Read(color_fill.raw, lcd_color_addr);          if (color_fill.is_enabled) { -            LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, textures[i]); +            LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i].texture);              // Resize the texture in case the framebuffer size has changed -            textures[i].width = 1; -            textures[i].height = 1; +            screen_infos[i].texture.width = 1; +            screen_infos[i].texture.height = 1;          } else { -            if (textures[i].width != (GLsizei)framebuffer.width || -                textures[i].height != (GLsizei)framebuffer.height || -                textures[i].format != framebuffer.color_format) { +            if (screen_infos[i].texture.width != (GLsizei)framebuffer.width || +                screen_infos[i].texture.height != (GLsizei)framebuffer.height || +                screen_infos[i].texture.format != framebuffer.color_format) {                  // Reallocate texture if the framebuffer size has changed.                  // This is expected to not happen very often and hence should not be a                  // performance problem. -                ConfigureFramebufferTexture(textures[i], framebuffer); +                ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer);              } -            LoadFBToActiveGLTexture(framebuffer, textures[i]); +            LoadFBToScreenInfo(framebuffer, screen_infos[i]);              // Resize the texture in case the framebuffer size has changed -            textures[i].width = framebuffer.width; -            textures[i].height = framebuffer.height; +            screen_infos[i].texture.width = framebuffer.width; +            screen_infos[i].texture.height = framebuffer.height;          }      } @@ -166,8 +166,8 @@ void RendererOpenGL::SwapBuffers() {  /**   * Loads framebuffer from emulated memory into the active OpenGL texture.   */ -void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, -                                             const TextureInfo& texture) { +void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, +                                         ScreenInfo& screen_info) {      const PAddr framebuffer_addr = framebuffer.active_fb == 0 ?              framebuffer.address_left1 : framebuffer.address_left2; @@ -177,8 +177,6 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig&          framebuffer_addr, (int)framebuffer.width,          (int)framebuffer.height, (int)framebuffer.format); -    const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); -      int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format);      size_t pixel_stride = framebuffer.stride / bpp; @@ -189,24 +187,34 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig&      // only allows rows to have a memory alignement of 4.      ASSERT(pixel_stride % 4 == 0); -    state.texture_units[0].texture_2d = texture.handle; -    state.Apply(); +    if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, pixel_stride, screen_info)) { +        // Reset the screen info's display texture to its own permanent texture +        screen_info.display_texture = screen_info.texture.resource.handle; +        screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); -    glActiveTexture(GL_TEXTURE0); -    glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); +        Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height); -    // Update existing texture -    // TODO: Test what happens on hardware when you change the framebuffer dimensions so that they -    //       differ from the LCD resolution. -    // TODO: Applications could theoretically crash Citra here by specifying too large -    //       framebuffer sizes. We should make sure that this cannot happen. -    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, -                    texture.gl_format, texture.gl_type, framebuffer_data); +        const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); -    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +        state.texture_units[0].texture_2d = screen_info.texture.resource.handle; +        state.Apply(); -    state.texture_units[0].texture_2d = 0; -    state.Apply(); +        glActiveTexture(GL_TEXTURE0); +        glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); + +        // Update existing texture +        // TODO: Test what happens on hardware when you change the framebuffer dimensions so that they +        //       differ from the LCD resolution. +        // TODO: Applications could theoretically crash Citra here by specifying too large +        //       framebuffer sizes. We should make sure that this cannot happen. +        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, +                        screen_info.texture.gl_format, screen_info.texture.gl_type, framebuffer_data); + +        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + +        state.texture_units[0].texture_2d = 0; +        state.Apply(); +    }  }  /** @@ -216,7 +224,7 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig&   */  void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b,                                                  const TextureInfo& texture) { -    state.texture_units[0].texture_2d = texture.handle; +    state.texture_units[0].texture_2d = texture.resource.handle;      state.Apply();      glActiveTexture(GL_TEXTURE0); @@ -224,6 +232,9 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color      // Update existing texture      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data); + +    state.texture_units[0].texture_2d = 0; +    state.Apply();  }  /** @@ -233,20 +244,22 @@ void RendererOpenGL::InitOpenGLObjects() {      glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f);      // Link shaders and get variable locations -    program_id = GLShader::LoadProgram(vertex_shader, fragment_shader); -    uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix"); -    uniform_color_texture = glGetUniformLocation(program_id, "color_texture"); -    attrib_position = glGetAttribLocation(program_id, "vert_position"); -    attrib_tex_coord = glGetAttribLocation(program_id, "vert_tex_coord"); +    shader.Create(vertex_shader, fragment_shader); +    state.draw.shader_program = shader.handle; +    state.Apply(); +    uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); +    uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); +    attrib_position = glGetAttribLocation(shader.handle, "vert_position"); +    attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord");      // Generate VBO handle for drawing -    glGenBuffers(1, &vertex_buffer_handle); +    vertex_buffer.Create();      // Generate VAO -    glGenVertexArrays(1, &vertex_array_handle); +    vertex_array.Create(); -    state.draw.vertex_array = vertex_array_handle; -    state.draw.vertex_buffer = vertex_buffer_handle; +    state.draw.vertex_array = vertex_array.handle; +    state.draw.vertex_buffer = vertex_buffer.handle;      state.draw.uniform_buffer = 0;      state.Apply(); @@ -258,13 +271,13 @@ void RendererOpenGL::InitOpenGLObjects() {      glEnableVertexAttribArray(attrib_tex_coord);      // Allocate textures for each screen -    for (auto& texture : textures) { -        glGenTextures(1, &texture.handle); +    for (auto& screen_info : screen_infos) { +        screen_info.texture.resource.Create();          // Allocation of storage is deferred until the first frame, when we          // know the framebuffer size. -        state.texture_units[0].texture_2d = texture.handle; +        state.texture_units[0].texture_2d = screen_info.texture.resource.handle;          state.Apply();          glActiveTexture(GL_TEXTURE0); @@ -273,6 +286,8 @@ void RendererOpenGL::InitOpenGLObjects() {          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); + +        screen_info.display_texture = screen_info.texture.resource.handle;      }      state.texture_units[0].texture_2d = 0; @@ -327,30 +342,38 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture,          UNIMPLEMENTED();      } -    state.texture_units[0].texture_2d = texture.handle; +    state.texture_units[0].texture_2d = texture.resource.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();  }  /**   * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD rotation.   */ -void RendererOpenGL::DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h) { +void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h) { +    auto& texcoords = screen_info.display_texcoords; +      std::array<ScreenRectVertex, 4> vertices = {{ -        ScreenRectVertex(x,   y,   1.f, 0.f), -        ScreenRectVertex(x+w, y,   1.f, 1.f), -        ScreenRectVertex(x,   y+h, 0.f, 0.f), -        ScreenRectVertex(x+w, y+h, 0.f, 1.f), +        ScreenRectVertex(x,   y,   texcoords.bottom, texcoords.left), +        ScreenRectVertex(x+w, y,   texcoords.bottom, texcoords.right), +        ScreenRectVertex(x,   y+h, texcoords.top, texcoords.left), +        ScreenRectVertex(x+w, y+h, texcoords.top, texcoords.right),      }}; -    state.texture_units[0].texture_2d = texture.handle; +    state.texture_units[0].texture_2d = screen_info.display_texture;      state.Apply();      glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());      glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + +    state.texture_units[0].texture_2d = 0; +    state.Apply();  }  /** @@ -362,9 +385,6 @@ void RendererOpenGL::DrawScreens() {      glViewport(0, 0, layout.width, layout.height);      glClear(GL_COLOR_BUFFER_BIT); -    state.draw.shader_program = program_id; -    state.Apply(); -      // Set projection matrix      std::array<GLfloat, 3 * 2> ortho_matrix = MakeOrthographicMatrix((float)layout.width,          (float)layout.height); @@ -374,9 +394,9 @@ void RendererOpenGL::DrawScreens() {      glActiveTexture(GL_TEXTURE0);      glUniform1i(uniform_color_texture, 0); -    DrawSingleScreenRotated(textures[0], (float)layout.top_screen.left, (float)layout.top_screen.top, +    DrawSingleScreenRotated(screen_infos[0], (float)layout.top_screen.left, (float)layout.top_screen.top,          (float)layout.top_screen.GetWidth(), (float)layout.top_screen.GetHeight()); -    DrawSingleScreenRotated(textures[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, +    DrawSingleScreenRotated(screen_infos[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top,          (float)layout.bottom_screen.GetWidth(), (float)layout.bottom_screen.GetHeight());      m_current_frame++; diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index fe4d142a5..5ca5255ac 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -11,10 +11,28 @@  #include "core/hw/gpu.h"  #include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_resource_manager.h"  #include "video_core/renderer_opengl/gl_state.h"  class EmuWindow; +/// Structure used for storing information about the textures for each 3DS screen +struct TextureInfo { +    OGLTexture resource; +    GLsizei width; +    GLsizei height; +    GPU::Regs::PixelFormat format; +    GLenum gl_format; +    GLenum gl_type; +}; + +/// Structure used for storing information about the display target for each 3DS screen +struct ScreenInfo { +    GLuint display_texture; +    MathUtil::Rectangle<float> display_texcoords; +    TextureInfo texture; +}; +  class RendererOpenGL : public RendererBase {  public: @@ -37,26 +55,16 @@ public:      void ShutDown() override;  private: -    /// Structure used for storing information about the textures for each 3DS screen -    struct TextureInfo { -        GLuint handle; -        GLsizei width; -        GLsizei height; -        GPU::Regs::PixelFormat format; -        GLenum gl_format; -        GLenum gl_type; -    }; -      void InitOpenGLObjects();      void ConfigureFramebufferTexture(TextureInfo& texture,                                       const GPU::Regs::FramebufferConfig& framebuffer);      void DrawScreens(); -    void DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h); +    void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h);      void UpdateFramerate(); -    // Loads framebuffer from emulated memory into the active OpenGL texture. -    void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, -                                 const TextureInfo& texture); +    // Loads framebuffer from emulated memory into the display information structure +    void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, +                             ScreenInfo& screen_info);      // Fills active OpenGL texture with the given RGB color.      void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b,                                      const TextureInfo& texture); @@ -69,10 +77,10 @@ private:      OpenGLState state;      // OpenGL object IDs -    GLuint vertex_array_handle; -    GLuint vertex_buffer_handle; -    GLuint program_id; -    std::array<TextureInfo, 2> textures;          ///< Textures for top and bottom screens respectively +    OGLVertexArray vertex_array; +    OGLBuffer vertex_buffer; +    OGLShader shader; +    std::array<ScreenInfo, 2> screen_infos;          ///< Display information for top and bottom screens respectively      // Shader uniform location indices      GLuint uniform_modelview_matrix;      GLuint uniform_color_texture; diff --git a/src/video_core/swrasterizer.h b/src/video_core/swrasterizer.h index 9a9a76d7a..090f899bc 100644 --- a/src/video_core/swrasterizer.h +++ b/src/video_core/swrasterizer.h @@ -11,16 +11,14 @@  namespace VideoCore {  class SWRasterizer : public RasterizerInterface { -    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 {}  };  } | 
