mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	HWRasterizer: Texture forwarding
This commit is contained in:
		
							parent
							
								
									e46d086189
								
							
						
					
					
						commit
						22f3a7e94c
					
				
					 20 changed files with 1749 additions and 970 deletions
				
			
		|  | @ -114,6 +114,7 @@ ResultVal<bool> File::SyncRequest() { | ||||||
|                 return read.Code(); |                 return read.Code(); | ||||||
|             } |             } | ||||||
|             cmd_buff[2] = static_cast<u32>(*read); |             cmd_buff[2] = static_cast<u32>(*read); | ||||||
|  |             Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length); | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| 
 | 
 | ||||||
| #include "common/bit_field.h" | #include "common/bit_field.h" | ||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
|  | #include "common/profiler.h" | ||||||
| 
 | 
 | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| #include "core/hle/kernel/event.h" | #include "core/hle/kernel/event.h" | ||||||
|  | @ -15,8 +16,6 @@ | ||||||
| 
 | 
 | ||||||
| #include "video_core/gpu_debugger.h" | #include "video_core/gpu_debugger.h" | ||||||
| #include "video_core/debug_utils/debug_utils.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" | #include "gsp_gpu.h" | ||||||
| 
 | 
 | ||||||
|  | @ -291,8 +290,6 @@ static void FlushDataCache(Service::Interface* self) { | ||||||
|     u32 size    = cmd_buff[2]; |     u32 size    = cmd_buff[2]; | ||||||
|     u32 process = cmd_buff[4]; |     u32 process = cmd_buff[4]; | ||||||
| 
 | 
 | ||||||
|     VideoCore::g_renderer->Rasterizer()->InvalidateRegion(Memory::VirtualToPhysicalAddress(address), size); |  | ||||||
| 
 |  | ||||||
|     // TODO(purpasmart96): Verify return header on HW
 |     // TODO(purpasmart96): Verify return header on HW
 | ||||||
| 
 | 
 | ||||||
|     cmd_buff[1] = RESULT_SUCCESS.raw; // No error
 |     cmd_buff[1] = RESULT_SUCCESS.raw; // No error
 | ||||||
|  | @ -408,6 +405,8 @@ void SignalInterrupt(InterruptId interrupt_id) { | ||||||
|     g_interrupt_event->Signal(); |     g_interrupt_event->Signal(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255)); | ||||||
|  | 
 | ||||||
| /// Executes the next GSP command
 | /// Executes the next GSP command
 | ||||||
| static void ExecuteCommand(const Command& command, u32 thread_id) { | static void ExecuteCommand(const Command& command, u32 thread_id) { | ||||||
|     // Utility function to convert register ID to address
 |     // 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
 |     // GX request DMA - typically used for copying memory from GSP heap to VRAM
 | ||||||
|     case CommandId::REQUEST_DMA: |     case CommandId::REQUEST_DMA: | ||||||
|         VideoCore::g_renderer->Rasterizer()->FlushRegion(Memory::VirtualToPhysicalAddress(command.dma_request.source_address), |     { | ||||||
|  |         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); |                             command.dma_request.size); | ||||||
| 
 | 
 | ||||||
|         memcpy(Memory::GetPointer(command.dma_request.dest_address), |         memcpy(Memory::GetPointer(command.dma_request.dest_address), | ||||||
|                Memory::GetPointer(command.dma_request.source_address), |                Memory::GetPointer(command.dma_request.source_address), | ||||||
|                command.dma_request.size); |                command.dma_request.size); | ||||||
|         SignalInterrupt(InterruptId::DMA); |         SignalInterrupt(InterruptId::DMA); | ||||||
| 
 |  | ||||||
|         VideoCore::g_renderer->Rasterizer()->InvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address), |  | ||||||
|                                                           command.dma_request.size); |  | ||||||
|         break; |         break; | ||||||
| 
 |     } | ||||||
|     // TODO: This will need some rework in the future. (why?)
 |     // TODO: This will need some rework in the future. (why?)
 | ||||||
|     case CommandId::SUBMIT_GPU_CMDLIST: |     case CommandId::SUBMIT_GPU_CMDLIST: | ||||||
|     { |     { | ||||||
|  | @ -517,13 +519,8 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { | ||||||
| 
 | 
 | ||||||
|     case CommandId::CACHE_FLUSH: |     case CommandId::CACHE_FLUSH: | ||||||
|     { |     { | ||||||
|         for (auto& region : command.cache_flush.regions) { |         // NOTE: Rasterizer flushing handled elsewhere in CPU read/write and other GPU handlers
 | ||||||
|             if (region.size == 0) |         // Use command.cache_flush.regions to implement this handler
 | ||||||
|                 break; |  | ||||||
| 
 |  | ||||||
|             VideoCore::g_renderer->Rasterizer()->InvalidateRegion( |  | ||||||
|                 Memory::VirtualToPhysicalAddress(region.address), region.size); |  | ||||||
|         } |  | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,9 +12,6 @@ | ||||||
| #include "core/hle/service/y2r_u.h" | #include "core/hle/service/y2r_u.h" | ||||||
| #include "core/hw/y2r.h" | #include "core/hw/y2r.h" | ||||||
| 
 | 
 | ||||||
| #include "video_core/renderer_base.h" |  | ||||||
| #include "video_core/video_core.h" |  | ||||||
| 
 |  | ||||||
| ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ////////////////////////////////////////////////////////////////////////////////////////////////////
 | ||||||
| // Namespace Y2R_U
 | // Namespace Y2R_U
 | ||||||
| 
 | 
 | ||||||
|  | @ -262,13 +259,12 @@ static void SetAlpha(Service::Interface* self) { | ||||||
| static void StartConversion(Service::Interface* self) { | static void StartConversion(Service::Interface* self) { | ||||||
|     u32* cmd_buff = Kernel::GetCommandBuffer(); |     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 :(
 |     // dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
 | ||||||
|     u32 total_output_size = conversion.input_lines * |     u32 total_output_size = conversion.input_lines * | ||||||
|         (conversion.dst.transfer_unit + conversion.dst.gap); |         (conversion.dst.transfer_unit + conversion.dst.gap); | ||||||
|     VideoCore::g_renderer->Rasterizer()->InvalidateRegion( |     Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); | ||||||
|         Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); | 
 | ||||||
|  |     HW::Y2R::PerformConversion(conversion); | ||||||
| 
 | 
 | ||||||
|     LOG_DEBUG(Service_Y2R, "called"); |     LOG_DEBUG(Service_Y2R, "called"); | ||||||
|     completion_event->Signal(); |     completion_event->Signal(); | ||||||
|  |  | ||||||
|  | @ -115,6 +115,18 @@ inline void Write(u32 addr, const T data) { | ||||||
|                 u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); |                 u8* start = Memory::GetPhysicalPointer(config.GetStartAddress()); | ||||||
|                 u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); |                 u8* end = Memory::GetPhysicalPointer(config.GetEndAddress()); | ||||||
| 
 | 
 | ||||||
|  |                 // 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) { |                     if (config.fill_24bit) { | ||||||
|                         // fill with 24-bit values
 |                         // fill with 24-bit values
 | ||||||
|                         for (u8* ptr = start; ptr < end; ptr += 3) { |                         for (u8* ptr = start; ptr < end; ptr += 3) { | ||||||
|  | @ -124,12 +136,18 @@ inline void Write(u32 addr, const T data) { | ||||||
|                         } |                         } | ||||||
|                     } else if (config.fill_32bit) { |                     } else if (config.fill_32bit) { | ||||||
|                         // fill with 32-bit values
 |                         // fill with 32-bit values
 | ||||||
|                     for (u32* ptr = (u32*)start; ptr < (u32*)end; ++ptr) |                         if (end > start) { | ||||||
|                         *ptr = config.value_32bit; |                             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 { |                     } else { | ||||||
|                         // fill with 16-bit values
 |                         // fill with 16-bit values
 | ||||||
|                     for (u16* ptr = (u16*)start; ptr < (u16*)end; ++ptr) |                         u16 value_16bit = config.value_16bit.Value(); | ||||||
|                         *ptr = config.value_16bit; |                         for (u8* ptr = start; ptr < end; ptr += sizeof(u16)) | ||||||
|  |                             memcpy(ptr, &value_16bit, sizeof(u16)); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 LOG_TRACE(HW_GPU, "MemoryFill from 0x%08x to 0x%08x", config.GetStartAddress(), config.GetEndAddress()); |                 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 { |                 } else { | ||||||
|                     GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PSC1); |                     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
 |             // Reset "trigger" flag and set the "finish" flag
 | ||||||
|  | @ -161,6 +177,7 @@ inline void Write(u32 addr, const T data) { | ||||||
|             if (Pica::g_debug_context) |             if (Pica::g_debug_context) | ||||||
|                 Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr); |                 Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr); | ||||||
| 
 | 
 | ||||||
|  |             if (!VideoCore::g_renderer->Rasterizer()->AccelerateDisplayTransfer(config)) { | ||||||
|                 u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); |                 u8* src_pointer = Memory::GetPhysicalPointer(config.GetPhysicalInputAddress()); | ||||||
|                 u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); |                 u8* dst_pointer = Memory::GetPhysicalPointer(config.GetPhysicalOutputAddress()); | ||||||
| 
 | 
 | ||||||
|  | @ -171,7 +188,10 @@ inline void Write(u32 addr, const T data) { | ||||||
|                     u32 output_gap = config.texture_copy.output_gap * 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); |                     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); |                     Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), contiguous_input_size); | ||||||
|  | 
 | ||||||
|  |                     size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); | ||||||
|  |                     Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size); | ||||||
| 
 | 
 | ||||||
|                     u32 remaining_size = config.texture_copy.size; |                     u32 remaining_size = config.texture_copy.size; | ||||||
|                     u32 remaining_input = input_width; |                     u32 remaining_input = input_width; | ||||||
|  | @ -203,9 +223,6 @@ inline void Write(u32 addr, const T data) { | ||||||
|                         config.GetPhysicalOutputAddress(), output_width, output_gap, |                         config.GetPhysicalOutputAddress(), output_width, output_gap, | ||||||
|                         config.flags); |                         config.flags); | ||||||
| 
 | 
 | ||||||
|                 size_t contiguous_output_size = config.texture_copy.size / output_width * (output_width + output_gap); |  | ||||||
|                 VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), contiguous_output_size); |  | ||||||
| 
 |  | ||||||
|                     GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); |                     GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
|  | @ -222,8 +239,8 @@ inline void Write(u32 addr, const T data) { | ||||||
|                     break; |                     break; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|             bool horizontal_scale = config.scaling != config.NoScale; |                 int horizontal_scale = config.scaling != config.NoScale ? 1 : 0; | ||||||
|             bool vertical_scale = config.scaling == config.ScaleXY; |                 int vertical_scale = config.scaling == config.ScaleXY ? 1 : 0; | ||||||
| 
 | 
 | ||||||
|                 u32 output_width = config.output_width >> horizontal_scale; |                 u32 output_width = config.output_width >> horizontal_scale; | ||||||
|                 u32 output_height = config.output_height >> vertical_scale; |                 u32 output_height = config.output_height >> vertical_scale; | ||||||
|  | @ -231,7 +248,8 @@ inline void Write(u32 addr, const T data) { | ||||||
|                 u32 input_size = config.input_width * config.input_height * GPU::Regs::BytesPerPixel(config.input_format); |                 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); |                 u32 output_size = output_width * output_height * GPU::Regs::BytesPerPixel(config.output_format); | ||||||
| 
 | 
 | ||||||
|             VideoCore::g_renderer->Rasterizer()->FlushRegion(config.GetPhysicalInputAddress(), input_size); |                 Memory::RasterizerFlushRegion(config.GetPhysicalInputAddress(), input_size); | ||||||
|  |                 Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(), output_size); | ||||||
| 
 | 
 | ||||||
|                 for (u32 y = 0; y < output_height; ++y) { |                 for (u32 y = 0; y < output_height; ++y) { | ||||||
|                     for (u32 x = 0; x < output_width; ++x) { |                     for (u32 x = 0; x < output_width; ++x) { | ||||||
|  | @ -334,11 +352,10 @@ inline void Write(u32 addr, const T data) { | ||||||
|                       config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), |                       config.GetPhysicalInputAddress(), config.input_width.Value(), config.input_height.Value(), | ||||||
|                       config.GetPhysicalOutputAddress(), output_width, output_height, |                       config.GetPhysicalOutputAddress(), output_width, output_height, | ||||||
|                       config.output_format.Value(), config.flags); |                       config.output_format.Value(), config.flags); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             g_regs.display_transfer_config.trigger = 0; |             g_regs.display_transfer_config.trigger = 0; | ||||||
|             GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); |             GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::PPF); | ||||||
| 
 |  | ||||||
|             VideoCore::g_renderer->Rasterizer()->InvalidateRegion(config.GetPhysicalOutputAddress(), output_size); |  | ||||||
|         } |         } | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -78,7 +78,7 @@ struct Regs { | ||||||
| 
 | 
 | ||||||
|     INSERT_PADDING_WORDS(0x4); |     INSERT_PADDING_WORDS(0x4); | ||||||
| 
 | 
 | ||||||
|     struct { |     struct MemoryFillConfig { | ||||||
|         u32 address_start; |         u32 address_start; | ||||||
|         u32 address_end; |         u32 address_end; | ||||||
| 
 | 
 | ||||||
|  | @ -165,7 +165,7 @@ struct Regs { | ||||||
| 
 | 
 | ||||||
|     INSERT_PADDING_WORDS(0x169); |     INSERT_PADDING_WORDS(0x169); | ||||||
| 
 | 
 | ||||||
|     struct { |     struct DisplayTransferConfig { | ||||||
|         u32 input_address; |         u32 input_address; | ||||||
|         u32 output_address; |         u32 output_address; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,6 +15,9 @@ | ||||||
| #include "core/memory_setup.h" | #include "core/memory_setup.h" | ||||||
| #include "core/mmio.h" | #include "core/mmio.h" | ||||||
| 
 | 
 | ||||||
|  | #include "video_core/renderer_base.h" | ||||||
|  | #include "video_core/video_core.h" | ||||||
|  | 
 | ||||||
| namespace Memory { | namespace Memory { | ||||||
| 
 | 
 | ||||||
| enum class PageType { | enum class PageType { | ||||||
|  | @ -22,8 +25,12 @@ enum class PageType { | ||||||
|     Unmapped, |     Unmapped, | ||||||
|     /// Page is mapped to regular memory. This is the only type you can get pointers to.
 |     /// Page is mapped to regular memory. This is the only type you can get pointers to.
 | ||||||
|     Memory, |     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.
 |     /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
 | ||||||
|     Special, |     Special, | ||||||
|  |     /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and invalidation
 | ||||||
|  |     RasterizerCachedSpecial, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct SpecialRegion { | struct SpecialRegion { | ||||||
|  | @ -57,6 +64,12 @@ struct PageTable { | ||||||
|      * the corresponding entry in `pointers` MUST be set to null. |      * the corresponding entry in `pointers` MUST be set to null. | ||||||
|      */ |      */ | ||||||
|     std::array<PageType, NUM_ENTRIES> attributes; |     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
 | /// 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) { |     while (base != end) { | ||||||
|         ASSERT_MSG(base < PageTable::NUM_ENTRIES, "out of range mapping at %08X", base); |         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->attributes[base] = type; | ||||||
|         current_page_table->pointers[base] = memory; |         current_page_table->pointers[base] = memory; | ||||||
|  |         current_page_table->cached_res_count[base] = 0; | ||||||
| 
 | 
 | ||||||
|         base += 1; |         base += 1; | ||||||
|         if (memory != nullptr) |         if (memory != nullptr) | ||||||
|  | @ -84,6 +104,7 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { | ||||||
| void InitMemoryMap() { | void InitMemoryMap() { | ||||||
|     main_page_table.pointers.fill(nullptr); |     main_page_table.pointers.fill(nullptr); | ||||||
|     main_page_table.attributes.fill(PageType::Unmapped); |     main_page_table.attributes.fill(PageType::Unmapped); | ||||||
|  |     main_page_table.cached_res_count.fill(0); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void MapMemoryRegion(VAddr base, u32 size, u8* target) { | void MapMemoryRegion(VAddr base, u32 size, u8* target) { | ||||||
|  | @ -106,6 +127,28 @@ void UnmapRegion(VAddr base, u32 size) { | ||||||
|     MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); |     MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) | ||||||
|  |  * using a VMA from the current process | ||||||
|  |  */ | ||||||
|  | static u8* GetPointerFromVMA(VAddr vaddr) { | ||||||
|  |     u8* direct_pointer = nullptr; | ||||||
|  | 
 | ||||||
|  |     auto& vma = Kernel::g_current_process->vm_manager.FindVMA(vaddr)->second; | ||||||
|  |     switch (vma.type) { | ||||||
|  |     case Kernel::VMAType::AllocatedMemoryBlock: | ||||||
|  |         direct_pointer = vma.backing_block->data() + vma.offset; | ||||||
|  |         break; | ||||||
|  |     case Kernel::VMAType::BackingMemory: | ||||||
|  |         direct_pointer = vma.backing_memory; | ||||||
|  |         break; | ||||||
|  |     default: | ||||||
|  |         UNREACHABLE(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return direct_pointer + (vaddr - vma.base); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**
 | /**
 | ||||||
|  * This function should only be called for virtual addreses with attribute `PageType::Special`. |  * This function should only be called for virtual addreses with attribute `PageType::Special`. | ||||||
|  */ |  */ | ||||||
|  | @ -126,6 +169,7 @@ template <typename T> | ||||||
| T Read(const VAddr vaddr) { | T Read(const VAddr vaddr) { | ||||||
|     const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; |     const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; | ||||||
|     if (page_pointer) { |     if (page_pointer) { | ||||||
|  |         // NOTE: Avoid adding any extra logic to this fast-path block
 | ||||||
|         T value; |         T value; | ||||||
|         std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); |         std::memcpy(&value, &page_pointer[vaddr & PAGE_MASK], sizeof(T)); | ||||||
|         return value; |         return value; | ||||||
|  | @ -139,8 +183,22 @@ T Read(const VAddr vaddr) { | ||||||
|     case PageType::Memory: |     case PageType::Memory: | ||||||
|         ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); |         ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); | ||||||
|         break; |         break; | ||||||
|  |     case PageType::RasterizerCachedMemory: | ||||||
|  |     { | ||||||
|  |         RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); | ||||||
|  | 
 | ||||||
|  |         T value; | ||||||
|  |         std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); | ||||||
|  |         return value; | ||||||
|  |     } | ||||||
|     case PageType::Special: |     case PageType::Special: | ||||||
|         return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); |         return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); | ||||||
|  |     case PageType::RasterizerCachedSpecial: | ||||||
|  |     { | ||||||
|  |         RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); | ||||||
|  | 
 | ||||||
|  |         return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|         UNREACHABLE(); |         UNREACHABLE(); | ||||||
|     } |     } | ||||||
|  | @ -153,6 +211,7 @@ template <typename T> | ||||||
| void Write(const VAddr vaddr, const T data) { | void Write(const VAddr vaddr, const T data) { | ||||||
|     u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; |     u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; | ||||||
|     if (page_pointer) { |     if (page_pointer) { | ||||||
|  |         // NOTE: Avoid adding any extra logic to this fast-path block
 | ||||||
|         std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); |         std::memcpy(&page_pointer[vaddr & PAGE_MASK], &data, sizeof(T)); | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  | @ -165,9 +224,23 @@ void Write(const VAddr vaddr, const T data) { | ||||||
|     case PageType::Memory: |     case PageType::Memory: | ||||||
|         ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); |         ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); | ||||||
|         break; |         break; | ||||||
|  |     case PageType::RasterizerCachedMemory: | ||||||
|  |     { | ||||||
|  |         RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); | ||||||
|  | 
 | ||||||
|  |         std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|     case PageType::Special: |     case PageType::Special: | ||||||
|         WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); |         WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); | ||||||
|         break; |         break; | ||||||
|  |     case PageType::RasterizerCachedSpecial: | ||||||
|  |     { | ||||||
|  |         RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); | ||||||
|  | 
 | ||||||
|  |         WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|     default: |     default: | ||||||
|         UNREACHABLE(); |         UNREACHABLE(); | ||||||
|     } |     } | ||||||
|  | @ -179,6 +252,10 @@ u8* GetPointer(const VAddr vaddr) { | ||||||
|         return page_pointer + (vaddr & PAGE_MASK); |         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); |     LOG_ERROR(HW_Memory, "unknown GetPointer @ 0x%08x", vaddr); | ||||||
|     return nullptr; |     return nullptr; | ||||||
| } | } | ||||||
|  | @ -187,6 +264,69 @@ u8* GetPhysicalPointer(PAddr address) { | ||||||
|     return GetPointer(PhysicalToVirtualAddress(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) { | u8 Read8(const VAddr addr) { | ||||||
|     return Read<u8>(addr); |     return Read<u8>(addr); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -148,4 +148,20 @@ VAddr PhysicalToVirtualAddress(PAddr addr); | ||||||
|  */ |  */ | ||||||
| u8* GetPhysicalPointer(PAddr address); | 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); | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -47,8 +47,8 @@ void DebugContext::OnEvent(Event event, void* data) { | ||||||
|     { |     { | ||||||
|         std::unique_lock<std::mutex> lock(breakpoint_mutex); |         std::unique_lock<std::mutex> lock(breakpoint_mutex); | ||||||
| 
 | 
 | ||||||
|         // Commit the hardware renderer's framebuffer so it will show on debug widgets
 |         // Commit the rasterizer's caches so framebuffers, render targets, etc. will show on debug widgets
 | ||||||
|         VideoCore::g_renderer->Rasterizer()->FlushFramebuffer(); |         VideoCore::g_renderer->Rasterizer()->FlushAll(); | ||||||
| 
 | 
 | ||||||
|         // TODO: Should stop the CPU thread here once we multithread emulation.
 |         // TODO: Should stop the CPU thread here once we multithread emulation.
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -577,7 +577,7 @@ struct Regs { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     struct { |     struct FramebufferConfig { | ||||||
|         INSERT_PADDING_WORDS(0x3); |         INSERT_PADDING_WORDS(0x3); | ||||||
| 
 | 
 | ||||||
|         union { |         union { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,10 @@ | ||||||
| 
 | 
 | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
| 
 | 
 | ||||||
|  | #include "core/hw/gpu.h" | ||||||
|  | 
 | ||||||
|  | struct ScreenInfo; | ||||||
|  | 
 | ||||||
| namespace Pica { | namespace Pica { | ||||||
| namespace Shader { | namespace Shader { | ||||||
| struct OutputVertex; | struct OutputVertex; | ||||||
|  | @ -18,12 +22,6 @@ class RasterizerInterface { | ||||||
| public: | public: | ||||||
|     virtual ~RasterizerInterface() {} |     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
 |     /// Queues the primitive formed by the given vertices for rendering
 | ||||||
|     virtual void AddTriangle(const Pica::Shader::OutputVertex& v0, |     virtual void AddTriangle(const Pica::Shader::OutputVertex& v0, | ||||||
|                              const Pica::Shader::OutputVertex& v1, |                              const Pica::Shader::OutputVertex& v1, | ||||||
|  | @ -32,17 +30,26 @@ public: | ||||||
|     /// Draw the current batch of triangles
 |     /// Draw the current batch of triangles
 | ||||||
|     virtual void DrawTriangles() = 0; |     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
 |     /// Notify rasterizer that the specified PICA register has been changed
 | ||||||
|     virtual void NotifyPicaRegisterChanged(u32 id) = 0; |     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; |     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.
 |     /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory and invalidated
 | ||||||
|     virtual void InvalidateRegion(PAddr addr, u32 size) = 0; |     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; } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,7 +21,5 @@ void RendererBase::RefreshRasterizerSetting() { | ||||||
|         } else { |         } else { | ||||||
|             rasterizer = std::make_unique<VideoCore::SWRasterizer>(); |             rasterizer = std::make_unique<VideoCore::SWRasterizer>(); | ||||||
|         } |         } | ||||||
|         rasterizer->InitObjects(); |  | ||||||
|         rasterizer->Reset(); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -36,10 +36,7 @@ static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { | ||||||
|             stage.GetAlphaMultiplier() == 1); |             stage.GetAlphaMultiplier() == 1); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| RasterizerOpenGL::RasterizerOpenGL() : cached_fb_color_addr(0), cached_fb_depth_addr(0) { } | RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { | ||||||
| RasterizerOpenGL::~RasterizerOpenGL() { } |  | ||||||
| 
 |  | ||||||
| void RasterizerOpenGL::InitObjects() { |  | ||||||
|     // Create sampler objects
 |     // Create sampler objects
 | ||||||
|     for (size_t i = 0; i < texture_samplers.size(); ++i) { |     for (size_t i = 0; i < texture_samplers.size(); ++i) { | ||||||
|         texture_samplers[i].Create(); |         texture_samplers[i].Create(); | ||||||
|  | @ -61,6 +58,10 @@ void RasterizerOpenGL::InitObjects() { | ||||||
| 
 | 
 | ||||||
|     uniform_block_data.dirty = true; |     uniform_block_data.dirty = true; | ||||||
| 
 | 
 | ||||||
|  |     for (unsigned index = 0; index < lighting_luts.size(); index++) { | ||||||
|  |         uniform_block_data.lut_dirty[index] = true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     // Set vertex attributes
 |     // Set vertex attributes
 | ||||||
|     glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); |     glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); | ||||||
|     glEnableVertexAttribArray(GLShader::ATTRIBUTE_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)); |     glVertexAttribPointer(GLShader::ATTRIBUTE_VIEW, 3, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, view)); | ||||||
|     glEnableVertexAttribArray(GLShader::ATTRIBUTE_VIEW); |     glEnableVertexAttribArray(GLShader::ATTRIBUTE_VIEW); | ||||||
| 
 | 
 | ||||||
|     SetShader(); |     // Create render framebuffer
 | ||||||
| 
 |  | ||||||
|     // 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
 |  | ||||||
|     framebuffer.Create(); |     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(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     glActiveTexture(GL_TEXTURE0); |     for (size_t i = 0; i < lighting_luts.size(); ++i) { | ||||||
|     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; |  | ||||||
| 
 |  | ||||||
|         glActiveTexture(GL_TEXTURE3 + 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); |         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_MIN_FILTER, GL_LINEAR); | ||||||
|         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |         glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | ||||||
|     } |     } | ||||||
|     state.Apply(); |  | ||||||
| 
 | 
 | ||||||
|     GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); |     // Sync fixed function OpenGL state
 | ||||||
|     ASSERT_MSG(status == GL_FRAMEBUFFER_COMPLETE, |  | ||||||
|                "OpenGL rasterizer framebuffer setup failed, status %X", status); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void RasterizerOpenGL::Reset() { |  | ||||||
|     SyncCullMode(); |     SyncCullMode(); | ||||||
|     SyncDepthModifiers(); |     SyncDepthModifiers(); | ||||||
|     SyncBlendEnabled(); |     SyncBlendEnabled(); | ||||||
|  | @ -156,10 +111,10 @@ void RasterizerOpenGL::Reset() { | ||||||
|     SyncColorWriteMask(); |     SyncColorWriteMask(); | ||||||
|     SyncStencilWriteMask(); |     SyncStencilWriteMask(); | ||||||
|     SyncDepthWriteMask(); |     SyncDepthWriteMask(); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     SetShader(); | RasterizerOpenGL::~RasterizerOpenGL() { | ||||||
| 
 | 
 | ||||||
|     res_cache.InvalidateAll(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | @ -196,47 +151,98 @@ void RasterizerOpenGL::DrawTriangles() { | ||||||
|     if (vertex_batch.empty()) |     if (vertex_batch.empty()) | ||||||
|         return; |         return; | ||||||
| 
 | 
 | ||||||
|     SyncFramebuffer(); |     const auto& regs = Pica::g_state.regs; | ||||||
|     SyncDrawState(); |  | ||||||
| 
 | 
 | ||||||
|     if (state.draw.shader_dirty) { |     // Sync and bind the framebuffer surfaces
 | ||||||
|         SetShader(); |     CachedSurface* color_surface; | ||||||
|         state.draw.shader_dirty = false; |     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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (unsigned index = 0; index < lighting_lut.size(); index++) { |     // Sync the viewport
 | ||||||
|  |     // These registers hold half-width and half-height, so must be multiplied by 2
 | ||||||
|  |     GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; | ||||||
|  |     GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; | ||||||
|  | 
 | ||||||
|  |     glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width), | ||||||
|  |                (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height), | ||||||
|  |                (GLsizei)(viewport_width * color_surface->res_scale_width), (GLsizei)(viewport_height * color_surface->res_scale_height)); | ||||||
|  | 
 | ||||||
|  |     // Sync and bind the texture surfaces
 | ||||||
|  |     const auto pica_textures = regs.GetTextures(); | ||||||
|  |     for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { | ||||||
|  |         const auto& texture = pica_textures[texture_index]; | ||||||
|  | 
 | ||||||
|  |         if (texture.enabled) { | ||||||
|  |             texture_samplers[texture_index].SyncWithConfig(texture.config); | ||||||
|  |             CachedSurface* surface = res_cache.GetTextureSurface(texture); | ||||||
|  |             if (surface != nullptr) { | ||||||
|  |                 state.texture_units[texture_index].texture_2d = surface->texture.handle; | ||||||
|  |             } else { | ||||||
|  |                 // Can occur when texture addr is null or its memory is unmapped/invalid
 | ||||||
|  |                 state.texture_units[texture_index].texture_2d = 0; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             state.texture_units[texture_index].texture_2d = 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Sync and bind the shader
 | ||||||
|  |     if (shader_dirty) { | ||||||
|  |         SetShader(); | ||||||
|  |         shader_dirty = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Sync the lighting luts
 | ||||||
|  |     for (unsigned index = 0; index < lighting_luts.size(); index++) { | ||||||
|         if (uniform_block_data.lut_dirty[index]) { |         if (uniform_block_data.lut_dirty[index]) { | ||||||
|             SyncLightingLUT(index); |             SyncLightingLUT(index); | ||||||
|             uniform_block_data.lut_dirty[index] = false; |             uniform_block_data.lut_dirty[index] = false; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     // Sync the uniform data
 | ||||||
|     if (uniform_block_data.dirty) { |     if (uniform_block_data.dirty) { | ||||||
|         glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), &uniform_block_data.data, GL_STATIC_DRAW); |         glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), &uniform_block_data.data, GL_STATIC_DRAW); | ||||||
|         uniform_block_data.dirty = false; |         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); |     glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), GL_STREAM_DRAW); | ||||||
|     glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size()); |     glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size()); | ||||||
| 
 | 
 | ||||||
|  |     // Mark framebuffer surfaces as dirty
 | ||||||
|  |     // TODO: Restrict invalidation area to the viewport
 | ||||||
|  |     if (color_surface != nullptr) { | ||||||
|  |         color_surface->dirty = true; | ||||||
|  |         res_cache.FlushRegion(color_surface->addr, color_surface->size, color_surface, true); | ||||||
|  |     } | ||||||
|  |     if (depth_surface != nullptr) { | ||||||
|  |         depth_surface->dirty = true; | ||||||
|  |         res_cache.FlushRegion(depth_surface->addr, depth_surface->size, depth_surface, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     vertex_batch.clear(); |     vertex_batch.clear(); | ||||||
| 
 | 
 | ||||||
|     // Flush the resource cache at the current depth and color framebuffer addresses for render-to-texture
 |     // Unbind textures for potential future use as framebuffer attachments
 | ||||||
|     const auto& regs = Pica::g_state.regs; |     for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { | ||||||
| 
 |         state.texture_units[texture_index].texture_2d = 0; | ||||||
|     u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) |     } | ||||||
|                                * fb_color_texture.width * fb_color_texture.height; |     state.Apply(); | ||||||
| 
 |  | ||||||
|     u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) |  | ||||||
|                                * fb_depth_texture.width * fb_depth_texture.height; |  | ||||||
| 
 |  | ||||||
|     res_cache.InvalidateInRange(cached_fb_color_addr, cached_fb_color_size, true); |  | ||||||
|     res_cache.InvalidateInRange(cached_fb_depth_addr, cached_fb_depth_size, true); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void RasterizerOpenGL::FlushFramebuffer() { |  | ||||||
|     CommitColorBuffer(); |  | ||||||
|     CommitDepthBuffer(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { | void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { | ||||||
|  | @ -268,7 +274,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { | ||||||
|     // Alpha test
 |     // Alpha test
 | ||||||
|     case PICA_REG_INDEX(output_merger.alpha_test): |     case PICA_REG_INDEX(output_merger.alpha_test): | ||||||
|         SyncAlphaTest(); |         SyncAlphaTest(); | ||||||
|         state.draw.shader_dirty = true; |         shader_dirty = true; | ||||||
|         break; |         break; | ||||||
| 
 | 
 | ||||||
|     // Sync GL stencil test + stencil write mask
 |     // 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_op): | ||||||
|     case PICA_REG_INDEX(tev_stage5.color_scale): |     case PICA_REG_INDEX(tev_stage5.color_scale): | ||||||
|     case PICA_REG_INDEX(tev_combiner_buffer_input): |     case PICA_REG_INDEX(tev_combiner_buffer_input): | ||||||
|         state.draw.shader_dirty = true; |         shader_dirty = true; | ||||||
|         break; |         break; | ||||||
|     case PICA_REG_INDEX(tev_stage0.const_r): |     case PICA_REG_INDEX(tev_stage0.const_r): | ||||||
|         SyncTevConstColor(0, regs.tev_stage0); |         SyncTevConstColor(0, regs.tev_stage0); | ||||||
|  | @ -521,41 +527,257 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { | void RasterizerOpenGL::FlushAll() { | ||||||
|     const auto& regs = Pica::g_state.regs; |     res_cache.FlushAll(); | ||||||
| 
 |  | ||||||
|     u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) |  | ||||||
|                                * fb_color_texture.width * fb_color_texture.height; |  | ||||||
| 
 |  | ||||||
|     u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) |  | ||||||
|                                * fb_depth_texture.width * fb_depth_texture.height; |  | ||||||
| 
 |  | ||||||
|     // If source memory region overlaps 3DS framebuffers, commit them before the copy happens
 |  | ||||||
|     if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size)) |  | ||||||
|         CommitColorBuffer(); |  | ||||||
| 
 |  | ||||||
|     if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) |  | ||||||
|         CommitDepthBuffer(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) { | 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) | void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { | ||||||
|                                * fb_color_texture.width * fb_color_texture.height; |     res_cache.FlushRegion(addr, size, nullptr, true); | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) | bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { | ||||||
|                                * fb_depth_texture.width * fb_depth_texture.height; |     using PixelFormat = CachedSurface::PixelFormat; | ||||||
|  |     using SurfaceType = CachedSurface::SurfaceType; | ||||||
| 
 | 
 | ||||||
|     // If modified memory region overlaps 3DS framebuffers, reload their contents into OpenGL
 |     if (config.is_texture_copy) { | ||||||
|     if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size)) |         // TODO(tfarley): Try to hardware accelerate this
 | ||||||
|         ReloadColorBuffer(); |         return false; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) |     CachedSurface src_params; | ||||||
|         ReloadDepthBuffer(); |     src_params.addr = config.GetPhysicalInputAddress(); | ||||||
|  |     src_params.width = config.output_width; | ||||||
|  |     src_params.height = config.output_height; | ||||||
|  |     src_params.is_tiled = !config.input_linear; | ||||||
|  |     src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.input_format); | ||||||
| 
 | 
 | ||||||
|     // Notify cache of flush in case the region touches a cached resource
 |     CachedSurface dst_params; | ||||||
|     res_cache.InvalidateInRange(addr, size); |     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; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { | ||||||
|  |     using PixelFormat = CachedSurface::PixelFormat; | ||||||
|  |     using SurfaceType = CachedSurface::SurfaceType; | ||||||
|  | 
 | ||||||
|  |     CachedSurface* dst_surface = res_cache.TryGetFillSurface(config); | ||||||
|  | 
 | ||||||
|  |     if (dst_surface == nullptr) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     OpenGLState cur_state = OpenGLState::GetCurState(); | ||||||
|  | 
 | ||||||
|  |     SurfaceType dst_type = CachedSurface::GetFormatType(dst_surface->pixel_format); | ||||||
|  | 
 | ||||||
|  |     GLuint old_fb = cur_state.draw.draw_framebuffer; | ||||||
|  |     cur_state.draw.draw_framebuffer = framebuffer.handle; | ||||||
|  |     // TODO: When scissor test is implemented, need to disable scissor test in cur_state here so Clear call isn't affected
 | ||||||
|  |     cur_state.Apply(); | ||||||
|  | 
 | ||||||
|  |     if (dst_type == SurfaceType::Color || dst_type == SurfaceType::Texture) { | ||||||
|  |         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_surface->texture.handle, 0); | ||||||
|  |         glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||||||
|  | 
 | ||||||
|  |         if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         GLfloat color_values[4] = {0.0f, 0.0f, 0.0f, 0.0f}; | ||||||
|  | 
 | ||||||
|  |         // TODO: Handle additional pixel format and fill value size combinations to accelerate more cases
 | ||||||
|  |         //       For instance, checking if fill value's bytes/bits repeat to allow filling I8/A8/I4/A4/...
 | ||||||
|  |         //       Currently only handles formats that are multiples of the fill value size
 | ||||||
|  | 
 | ||||||
|  |         if (config.fill_24bit) { | ||||||
|  |             switch (dst_surface->pixel_format) { | ||||||
|  |             case PixelFormat::RGB8: | ||||||
|  |                 color_values[0] = config.value_24bit_r / 255.0f; | ||||||
|  |                 color_values[1] = config.value_24bit_g / 255.0f; | ||||||
|  |                 color_values[2] = config.value_24bit_b / 255.0f; | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } else if (config.fill_32bit) { | ||||||
|  |             u32 value = config.value_32bit; | ||||||
|  | 
 | ||||||
|  |             switch (dst_surface->pixel_format) { | ||||||
|  |             case PixelFormat::RGBA8: | ||||||
|  |                 color_values[0] = (value >> 24) / 255.0f; | ||||||
|  |                 color_values[1] = ((value >> 16) & 0xFF) / 255.0f; | ||||||
|  |                 color_values[2] = ((value >> 8) & 0xFF) / 255.0f; | ||||||
|  |                 color_values[3] = (value & 0xFF) / 255.0f; | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             u16 value_16bit = config.value_16bit.Value(); | ||||||
|  |             Math::Vec4<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); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     cur_state.draw.draw_framebuffer = old_fb; | ||||||
|  |     // TODO: Return scissor test to previous value when scissor test is implemented
 | ||||||
|  |     cur_state.Apply(); | ||||||
|  | 
 | ||||||
|  |     dst_surface->dirty = true; | ||||||
|  |     res_cache.FlushRegion(dst_surface->addr, dst_surface->size, dst_surface, true); | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { | ||||||
|  |     if (framebuffer_addr == 0) { | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     CachedSurface src_params; | ||||||
|  |     src_params.addr = framebuffer_addr; | ||||||
|  |     src_params.width = config.width; | ||||||
|  |     src_params.height = config.height; | ||||||
|  |     src_params.stride = pixel_stride; | ||||||
|  |     src_params.is_tiled = false; | ||||||
|  |     src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.color_format); | ||||||
|  | 
 | ||||||
|  |     MathUtil::Rectangle<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() { | 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() { | void RasterizerOpenGL::SetShader() { | ||||||
|     PicaShaderConfig config = PicaShaderConfig::CurrentConfig(); |     PicaShaderConfig config = PicaShaderConfig::CurrentConfig(); | ||||||
|     std::unique_ptr<PicaShader> shader = std::make_unique<PicaShader>(); |     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() { | void RasterizerOpenGL::SyncCullMode() { | ||||||
|     const auto& regs = Pica::g_state.regs; |     const auto& regs = Pica::g_state.regs; | ||||||
| 
 | 
 | ||||||
|  | @ -1034,229 +1077,3 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) { | ||||||
|         uniform_block_data.dirty = true; |         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); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ | ||||||
| #include "video_core/renderer_opengl/gl_rasterizer_cache.h" | #include "video_core/renderer_opengl/gl_rasterizer_cache.h" | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| #include "video_core/renderer_opengl/pica_to_gl.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" | #include "video_core/shader/shader_interpreter.h" | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  | @ -191,16 +192,17 @@ public: | ||||||
|     RasterizerOpenGL(); |     RasterizerOpenGL(); | ||||||
|     ~RasterizerOpenGL() override; |     ~RasterizerOpenGL() override; | ||||||
| 
 | 
 | ||||||
|     void InitObjects() override; |  | ||||||
|     void Reset() override; |  | ||||||
|     void AddTriangle(const Pica::Shader::OutputVertex& v0, |     void AddTriangle(const Pica::Shader::OutputVertex& v0, | ||||||
|                      const Pica::Shader::OutputVertex& v1, |                      const Pica::Shader::OutputVertex& v1, | ||||||
|                      const Pica::Shader::OutputVertex& v2) override; |                      const Pica::Shader::OutputVertex& v2) override; | ||||||
|     void DrawTriangles() override; |     void DrawTriangles() override; | ||||||
|     void FlushFramebuffer() override; |  | ||||||
|     void NotifyPicaRegisterChanged(u32 id) override; |     void NotifyPicaRegisterChanged(u32 id) override; | ||||||
|  |     void FlushAll() override; | ||||||
|     void FlushRegion(PAddr addr, u32 size) 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
 |     /// OpenGL shader generated for a given Pica register state
 | ||||||
|     struct PicaShader { |     struct PicaShader { | ||||||
|  | @ -210,26 +212,6 @@ public: | ||||||
| 
 | 
 | ||||||
| private: | 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 { |     struct SamplerInfo { | ||||||
|         using TextureConfig = Pica::Regs::TextureConfig; |         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) == 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"); |     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
 |     /// Sets the OpenGL shader in accordance with the current PICA register state
 | ||||||
|     void SetShader(); |     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
 |     /// Syncs the cull mode to match the PICA register
 | ||||||
|     void SyncCullMode(); |     void SyncCullMode(); | ||||||
| 
 | 
 | ||||||
|  | @ -386,45 +359,15 @@ private: | ||||||
|     /// Syncs the specified light's specular 1 color to match the PICA register
 |     /// Syncs the specified light's specular 1 color to match the PICA register
 | ||||||
|     void SyncLightSpecular1(int light_index); |     void SyncLightSpecular1(int light_index); | ||||||
| 
 | 
 | ||||||
|     /// Syncs the remaining OpenGL drawing state to match the current PICA state
 |     OpenGLState 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(); |  | ||||||
| 
 | 
 | ||||||
|     RasterizerCacheOpenGL res_cache; |     RasterizerCacheOpenGL res_cache; | ||||||
| 
 | 
 | ||||||
|     std::vector<HardwareVertex> vertex_batch; |     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; |     std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache; | ||||||
|     const PicaShader* current_shader = nullptr; |     const PicaShader* current_shader = nullptr; | ||||||
|  |     bool shader_dirty; | ||||||
| 
 | 
 | ||||||
|     struct { |     struct { | ||||||
|         UniformData data; |         UniformData data; | ||||||
|  | @ -432,11 +375,12 @@ private: | ||||||
|         bool dirty; |         bool dirty; | ||||||
|     } uniform_block_data; |     } uniform_block_data; | ||||||
| 
 | 
 | ||||||
|  |     std::array<SamplerInfo, 3> texture_samplers; | ||||||
|     OGLVertexArray vertex_array; |     OGLVertexArray vertex_array; | ||||||
|     OGLBuffer vertex_buffer; |     OGLBuffer vertex_buffer; | ||||||
|     OGLBuffer uniform_buffer; |     OGLBuffer uniform_buffer; | ||||||
|     OGLFramebuffer framebuffer; |     OGLFramebuffer framebuffer; | ||||||
| 
 | 
 | ||||||
|     std::array<OGLTexture, 6> lighting_lut; |     std::array<OGLTexture, 6> lighting_luts; | ||||||
|     std::array<std::array<GLvec4, 256>, 6> lighting_lut_data; |     std::array<std::array<GLvec4, 256>, 6> lighting_lut_data; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -2,8 +2,9 @@ | ||||||
| // Licensed under GPLv2 or any later version
 | // Licensed under GPLv2 or any later version
 | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include <memory> | #include <unordered_set> | ||||||
| 
 | 
 | ||||||
|  | #include "common/emu_window.h" | ||||||
| #include "common/hash.h" | #include "common/hash.h" | ||||||
| #include "common/math_util.h" | #include "common/math_util.h" | ||||||
| #include "common/microprofile.h" | #include "common/microprofile.h" | ||||||
|  | @ -12,71 +13,693 @@ | ||||||
| #include "core/memory.h" | #include "core/memory.h" | ||||||
| 
 | 
 | ||||||
| #include "video_core/debug_utils/debug_utils.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/gl_rasterizer_cache.h" | ||||||
| #include "video_core/renderer_opengl/pica_to_gl.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() { | RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { | ||||||
|     InvalidateAll(); |     FlushAll(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| MICROPROFILE_DEFINE(OpenGL_TextureUpload, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); | static void MortonCopyPixels(CachedSurface::PixelFormat pixel_format, u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, bool morton_to_gl) { | ||||||
|  |     using PixelFormat = CachedSurface::PixelFormat; | ||||||
| 
 | 
 | ||||||
| void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info) { |     u8* data_ptrs[2]; | ||||||
|     const auto cached_texture = texture_cache.find(info.physical_address); |     u32 depth_stencil_shifts[2] = {24, 8}; | ||||||
| 
 | 
 | ||||||
|     if (cached_texture != texture_cache.end()) { |     if (morton_to_gl) { | ||||||
|         state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle; |         std::swap(depth_stencil_shifts[0], depth_stencil_shifts[1]); | ||||||
|         state.Apply(); |     } | ||||||
|  | 
 | ||||||
|  |     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]; | ||||||
|  | 
 | ||||||
|  |                 // 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 { |     } 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; | ||||||
| 
 | 
 | ||||||
|         std::unique_ptr<CachedTexture> new_texture = std::make_unique<CachedTexture>(); |                 data_ptrs[morton_to_gl] = morton_data + morton_offset; | ||||||
|  |                 data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; | ||||||
| 
 | 
 | ||||||
|         new_texture->texture.Create(); |                 memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); | ||||||
|         state.texture_units[texture_unit].texture_2d = new_texture->texture.handle; |  | ||||||
|         state.Apply(); |  | ||||||
|         glActiveTexture(GL_TEXTURE0 + texture_unit); |  | ||||||
| 
 |  | ||||||
|         u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address); |  | ||||||
| 
 |  | ||||||
|         new_texture->width = info.width; |  | ||||||
|         new_texture->height = info.height; |  | ||||||
|         new_texture->size = info.stride * info.height; |  | ||||||
|         new_texture->addr = info.physical_address; |  | ||||||
|         new_texture->hash = Common::ComputeHash64(texture_src_data, new_texture->size); |  | ||||||
| 
 |  | ||||||
|         std::unique_ptr<Math::Vec4<u8>[]> temp_texture_buffer_rgba(new Math::Vec4<u8>[info.width * info.height]); |  | ||||||
| 
 |  | ||||||
|         for (int y = 0; y < info.height; ++y) { |  | ||||||
|             for (int x = 0; x < info.width; ++x) { |  | ||||||
|                 temp_texture_buffer_rgba[x + info.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, info.height - 1 - y, info); |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get()); |  | ||||||
| 
 |  | ||||||
|         texture_cache.emplace(info.physical_address, std::move(new_texture)); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void RasterizerCacheOpenGL::InvalidateInRange(PAddr addr, u32 size, bool ignore_hash) { | bool RasterizerCacheOpenGL::BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& dst_rect) { | ||||||
|     // TODO: Optimize by also inserting upper bound (addr + size) of each texture into the same map and also narrow using lower_bound
 |     using SurfaceType = CachedSurface::SurfaceType; | ||||||
|     auto cache_upper_bound = texture_cache.upper_bound(addr + size); |  | ||||||
| 
 | 
 | ||||||
|     for (auto it = texture_cache.begin(); it != cache_upper_bound;) { |     OpenGLState cur_state = OpenGLState::GetCurState(); | ||||||
|         const auto& info = *it->second; |  | ||||||
| 
 | 
 | ||||||
|         // Flush the texture only if the memory region intersects and a change is detected
 |     // Make sure textures aren't bound to texture units, since going to bind them to framebuffer components
 | ||||||
|         if (MathUtil::IntervalsIntersect(addr, size, info.addr, info.size) && |     OpenGLState::ResetTexture(src_tex); | ||||||
|             (ignore_hash || info.hash != Common::ComputeHash64(Memory::GetPhysicalPointer(info.addr), info.size))) { |     OpenGLState::ResetTexture(dst_tex); | ||||||
| 
 | 
 | ||||||
|             it = texture_cache.erase(it); |     // 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 { |     } else { | ||||||
|             ++it; |         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); | ||||||
|  | 
 | ||||||
|  |         // If not 1x scale, blit 1x texture to a new scaled texture and replace texture in surface
 | ||||||
|  |         if (new_surface->res_scale_width != 1.f || new_surface->res_scale_height != 1.f) { | ||||||
|  |             OGLTexture scaled_texture; | ||||||
|  |             scaled_texture.Create(); | ||||||
|  | 
 | ||||||
|  |             AllocateSurfaceTexture(scaled_texture.handle, new_surface->pixel_format, new_surface->GetScaledWidth(), new_surface->GetScaledHeight()); | ||||||
|  |             BlitTextures(new_surface->texture.handle, scaled_texture.handle, CachedSurface::GetFormatType(new_surface->pixel_format), | ||||||
|  |                 MathUtil::Rectangle<int>(0, 0, new_surface->width, new_surface->height), | ||||||
|  |                 MathUtil::Rectangle<int>(0, 0, new_surface->GetScaledWidth(), new_surface->GetScaledHeight())); | ||||||
|  | 
 | ||||||
|  |             new_surface->texture.Release(); | ||||||
|  |             new_surface->texture.handle = scaled_texture.handle; | ||||||
|  |             scaled_texture.handle = 0; | ||||||
|  |             cur_state.texture_units[0].texture_2d = new_surface->texture.handle; | ||||||
|  |             cur_state.Apply(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | ||||||
|  |         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||||||
|  |         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||||||
|  |         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||||||
|  | 
 | ||||||
|  |         cur_state.texture_units[0].texture_2d = old_tex; | ||||||
|  |         cur_state.Apply(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     Memory::RasterizerMarkRegionCached(new_surface->addr, new_surface->size, 1); | ||||||
|  |     surface_cache.add(std::make_pair(boost::icl::interval<PAddr>::right_open(new_surface->addr, new_surface->addr + new_surface->size), std::set<std::shared_ptr<CachedSurface>>({ new_surface }))); | ||||||
|  |     return new_surface.get(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | 
 | ||||||
|  |     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(); | ||||||
|  | 
 | ||||||
|  |             // 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)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         out_rect.left = (int)(out_rect.left * best_subrect_surface->res_scale_width); | ||||||
|  |         out_rect.right = (int)(out_rect.right * best_subrect_surface->res_scale_width); | ||||||
|  |         out_rect.top = (int)(out_rect.top * best_subrect_surface->res_scale_height); | ||||||
|  |         out_rect.bottom = (int)(out_rect.bottom * best_subrect_surface->res_scale_height); | ||||||
|  | 
 | ||||||
|  |         return best_subrect_surface; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // No subrect found - create and return a new surface
 | ||||||
|  |     if (!params.is_tiled) { | ||||||
|  |         out_rect = MathUtil::Rectangle<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); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | 
 | ||||||
|  |     if (color_surface != nullptr && depth_surface != nullptr && (depth_rect.left != color_rect.left || depth_rect.top != color_rect.top)) { | ||||||
|  |         // Can't specify separate color and depth viewport offsets in OpenGL, so re-zero both if they don't match
 | ||||||
|  |         if (color_rect.left != 0 || color_rect.top != 0) { | ||||||
|  |             color_surface = GetSurface(color_params, true, true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (depth_rect.left != 0 || depth_rect.top != 0) { | ||||||
|  |             depth_surface = GetSurface(depth_params, true, true); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!color_surface->is_tiled) { | ||||||
|  |             rect = MathUtil::Rectangle<int>(0, 0, (int)(color_params.width * color_params.res_scale_width), (int)(color_params.height * color_params.res_scale_height)); | ||||||
|  |         } else { | ||||||
|  |             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); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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::InvalidateAll() { | void RasterizerCacheOpenGL::FlushAll() { | ||||||
|     texture_cache.clear(); |     for (auto& surfaces : surface_cache) { | ||||||
|  |         for (auto& surface : surfaces.second) { | ||||||
|  |             FlushSurface(surface.get()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -6,38 +6,211 @@ | ||||||
| 
 | 
 | ||||||
| #include <map> | #include <map> | ||||||
| #include <memory> | #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/pica.h" | ||||||
| #include "video_core/debug_utils/debug_utils.h" | #include "video_core/debug_utils/debug_utils.h" | ||||||
| #include "video_core/renderer_opengl/gl_resource_manager.h" | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| 
 | 
 | ||||||
| class RasterizerCacheOpenGL : NonCopyable { | struct CachedSurface; | ||||||
| public: |  | ||||||
|     ~RasterizerCacheOpenGL(); |  | ||||||
| 
 | 
 | ||||||
|     /// Loads a texture from 3DS memory to OpenGL and caches it (if not already cached)
 | using SurfaceCache = boost::icl::interval_map<PAddr, std::set<std::shared_ptr<CachedSurface>>>; | ||||||
|     void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info); |  | ||||||
| 
 | 
 | ||||||
|     void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::Regs::FullTextureConfig& config) { | struct CachedSurface { | ||||||
|         LoadAndBindTexture(state, texture_unit, Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format)); |     enum class PixelFormat { | ||||||
|     } |         // First 5 formats are shared between textures and color buffers
 | ||||||
|  |         RGBA8        =  0, | ||||||
|  |         RGB8         =  1, | ||||||
|  |         RGB5A1       =  2, | ||||||
|  |         RGB565       =  3, | ||||||
|  |         RGBA4        =  4, | ||||||
| 
 | 
 | ||||||
|     /// Invalidate any cached resource intersecting the specified region.
 |         // Texture-only formats
 | ||||||
|     void InvalidateInRange(PAddr addr, u32 size, bool ignore_hash = false); |         IA8          =  5, | ||||||
|  |         RG8          =  6, | ||||||
|  |         I8           =  7, | ||||||
|  |         A8           =  8, | ||||||
|  |         IA4          =  9, | ||||||
|  |         I4           = 10, | ||||||
|  |         A4           = 11, | ||||||
|  |         ETC1         = 12, | ||||||
|  |         ETC1A4       = 13, | ||||||
| 
 | 
 | ||||||
|     /// Invalidate all cached OpenGL resources tracked by this cache manager
 |         // Depth buffer-only formats
 | ||||||
|     void InvalidateAll(); |         D16          = 14, | ||||||
|  |         // gap
 | ||||||
|  |         D24          = 16, | ||||||
|  |         D24S8        = 17, | ||||||
| 
 | 
 | ||||||
| private: |         Invalid      = 255, | ||||||
|     struct CachedTexture { |  | ||||||
|         OGLTexture texture; |  | ||||||
|         GLuint width; |  | ||||||
|         GLuint height; |  | ||||||
|         u32 size; |  | ||||||
|         u64 hash; |  | ||||||
|         PAddr addr; |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     std::map<PAddr, std::unique_ptr<CachedTexture>> texture_cache; |     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)
 | ||||||
|  |     CachedSurface* GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create); | ||||||
|  | 
 | ||||||
|  |     /// 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); | ||||||
|  | 
 | ||||||
|  |     /// Gets a surface based on the texture configuration
 | ||||||
|  |     CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config); | ||||||
|  | 
 | ||||||
|  |     /// 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); | ||||||
|  | 
 | ||||||
|  |     /// 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); | ||||||
|  | 
 | ||||||
|  |     /// 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]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ | ||||||
| // Refer to the license.txt file included.
 | // Refer to the license.txt file included.
 | ||||||
| 
 | 
 | ||||||
| #include "video_core/pica.h" | #include "video_core/pica.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| 
 | 
 | ||||||
| OpenGLState OpenGLState::cur_state; | OpenGLState OpenGLState::cur_state; | ||||||
|  | @ -48,17 +49,19 @@ OpenGLState::OpenGLState() { | ||||||
|         texture_unit.sampler = 0; |         texture_unit.sampler = 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     for (auto& lut : lighting_lut) { |     for (auto& lut : lighting_luts) { | ||||||
|         lut.texture_1d = 0; |         lut.texture_1d = 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     draw.framebuffer = 0; |     draw.read_framebuffer = 0; | ||||||
|  |     draw.draw_framebuffer = 0; | ||||||
|     draw.vertex_array = 0; |     draw.vertex_array = 0; | ||||||
|     draw.vertex_buffer = 0; |     draw.vertex_buffer = 0; | ||||||
|  |     draw.uniform_buffer = 0; | ||||||
|     draw.shader_program = 0; |     draw.shader_program = 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OpenGLState::Apply() { | void OpenGLState::Apply() const { | ||||||
|     // Culling
 |     // Culling
 | ||||||
|     if (cull.enabled != cur_state.cull.enabled) { |     if (cull.enabled != cur_state.cull.enabled) { | ||||||
|         if (cull.enabled) { |         if (cull.enabled) { | ||||||
|  | @ -175,16 +178,19 @@ void OpenGLState::Apply() { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Lighting LUTs
 |     // Lighting LUTs
 | ||||||
|     for (unsigned i = 0; i < ARRAY_SIZE(lighting_lut); ++i) { |     for (unsigned i = 0; i < ARRAY_SIZE(lighting_luts); ++i) { | ||||||
|         if (lighting_lut[i].texture_1d != cur_state.lighting_lut[i].texture_1d) { |         if (lighting_luts[i].texture_1d != cur_state.lighting_luts[i].texture_1d) { | ||||||
|             glActiveTexture(GL_TEXTURE3 + i); |             glActiveTexture(GL_TEXTURE3 + i); | ||||||
|             glBindTexture(GL_TEXTURE_1D, lighting_lut[i].texture_1d); |             glBindTexture(GL_TEXTURE_1D, lighting_luts[i].texture_1d); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Framebuffer
 |     // Framebuffer
 | ||||||
|     if (draw.framebuffer != cur_state.draw.framebuffer) { |     if (draw.read_framebuffer != cur_state.draw.read_framebuffer) { | ||||||
|         glBindFramebuffer(GL_FRAMEBUFFER, draw.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
 |     // Vertex array
 | ||||||
|  | @ -210,45 +216,58 @@ void OpenGLState::Apply() { | ||||||
|     cur_state = *this; |     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) { |     for (auto& unit : cur_state.texture_units) { | ||||||
|         if (unit.texture_2d == id) { |         if (unit.texture_2d == handle) { | ||||||
|             unit.texture_2d = 0; |             unit.texture_2d = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OpenGLState::ResetSampler(GLuint id) { | void OpenGLState::ResetSampler(GLuint handle) { | ||||||
|     for (auto& unit : cur_state.texture_units) { |     for (auto& unit : cur_state.texture_units) { | ||||||
|         if (unit.sampler == id) { |         if (unit.sampler == handle) { | ||||||
|             unit.sampler = 0; |             unit.sampler = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OpenGLState::ResetProgram(GLuint id) { | void OpenGLState::ResetProgram(GLuint handle) { | ||||||
|     if (cur_state.draw.shader_program == id) { |     if (cur_state.draw.shader_program == handle) { | ||||||
|         cur_state.draw.shader_program = 0; |         cur_state.draw.shader_program = 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OpenGLState::ResetBuffer(GLuint id) { | void OpenGLState::ResetBuffer(GLuint handle) { | ||||||
|     if (cur_state.draw.vertex_buffer == id) { |     if (cur_state.draw.vertex_buffer == handle) { | ||||||
|         cur_state.draw.vertex_buffer = 0; |         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; |         cur_state.draw.uniform_buffer = 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OpenGLState::ResetVertexArray(GLuint id) { | void OpenGLState::ResetVertexArray(GLuint handle) { | ||||||
|     if (cur_state.draw.vertex_array == id) { |     if (cur_state.draw.vertex_array == handle) { | ||||||
|         cur_state.draw.vertex_array = 0; |         cur_state.draw.vertex_array = 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void OpenGLState::ResetFramebuffer(GLuint id) { | void OpenGLState::ResetFramebuffer(GLuint handle) { | ||||||
|     if (cur_state.draw.framebuffer == id) { |     if (cur_state.draw.read_framebuffer == handle) { | ||||||
|         cur_state.draw.framebuffer = 0; |         cur_state.draw.read_framebuffer = 0; | ||||||
|  |     } | ||||||
|  |     if (cur_state.draw.draw_framebuffer == handle) { | ||||||
|  |         cur_state.draw.draw_framebuffer = 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ | ||||||
| #pragma once | #pragma once | ||||||
| 
 | 
 | ||||||
| #include <glad/glad.h> | #include <glad/glad.h> | ||||||
|  | #include <memory> | ||||||
| 
 | 
 | ||||||
| class OpenGLState { | class OpenGLState { | ||||||
| public: | public: | ||||||
|  | @ -63,15 +64,15 @@ public: | ||||||
| 
 | 
 | ||||||
|     struct { |     struct { | ||||||
|         GLuint texture_1d; // GL_TEXTURE_BINDING_1D
 |         GLuint texture_1d; // GL_TEXTURE_BINDING_1D
 | ||||||
|     } lighting_lut[6]; |     } lighting_luts[6]; | ||||||
| 
 | 
 | ||||||
|     struct { |     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_array; // GL_VERTEX_ARRAY_BINDING
 | ||||||
|         GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING
 |         GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING
 | ||||||
|         GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING
 |         GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING
 | ||||||
|         GLuint shader_program; // GL_CURRENT_PROGRAM
 |         GLuint shader_program; // GL_CURRENT_PROGRAM
 | ||||||
|         bool shader_dirty; |  | ||||||
|     } draw; |     } draw; | ||||||
| 
 | 
 | ||||||
|     OpenGLState(); |     OpenGLState(); | ||||||
|  | @ -82,14 +83,18 @@ public: | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Apply this state as the current OpenGL state
 |     /// Apply this state as the current OpenGL state
 | ||||||
|     void Apply(); |     void Apply() const; | ||||||
| 
 | 
 | ||||||
|     static void ResetTexture(GLuint id); |     /// Check the status of the current OpenGL read or draw framebuffer configuration
 | ||||||
|     static void ResetSampler(GLuint id); |     static GLenum CheckFBStatus(GLenum target); | ||||||
|     static void ResetProgram(GLuint id); | 
 | ||||||
|     static void ResetBuffer(GLuint id); |     /// Resets and unbinds any references to the given resource in the current OpenGL state
 | ||||||
|     static void ResetVertexArray(GLuint id); |     static void ResetTexture(GLuint handle); | ||||||
|     static void ResetFramebuffer(GLuint id); |     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: | private: | ||||||
|     static OpenGLState cur_state; |     static OpenGLState cur_state; | ||||||
|  |  | ||||||
|  | @ -107,7 +107,7 @@ void RendererOpenGL::SwapBuffers() { | ||||||
|     OpenGLState prev_state = OpenGLState::GetCurState(); |     OpenGLState prev_state = OpenGLState::GetCurState(); | ||||||
|     state.Apply(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     for(int i : {0, 1}) { |     for (int i : {0, 1}) { | ||||||
|         const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; |         const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; | ||||||
| 
 | 
 | ||||||
|         // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
 |         // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04
 | ||||||
|  | @ -117,25 +117,25 @@ void RendererOpenGL::SwapBuffers() { | ||||||
|         LCD::Read(color_fill.raw, lcd_color_addr); |         LCD::Read(color_fill.raw, lcd_color_addr); | ||||||
| 
 | 
 | ||||||
|         if (color_fill.is_enabled) { |         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
 |             // Resize the texture in case the framebuffer size has changed
 | ||||||
|             textures[i].width = 1; |             screen_infos[i].texture.width = 1; | ||||||
|             textures[i].height = 1; |             screen_infos[i].texture.height = 1; | ||||||
|         } else { |         } else { | ||||||
|             if (textures[i].width != (GLsizei)framebuffer.width || |             if (screen_infos[i].texture.width != (GLsizei)framebuffer.width || | ||||||
|                 textures[i].height != (GLsizei)framebuffer.height || |                 screen_infos[i].texture.height != (GLsizei)framebuffer.height || | ||||||
|                 textures[i].format != framebuffer.color_format) { |                 screen_infos[i].texture.format != framebuffer.color_format) { | ||||||
|                 // Reallocate texture if the framebuffer size has changed.
 |                 // Reallocate texture if the framebuffer size has changed.
 | ||||||
|                 // This is expected to not happen very often and hence should not be a
 |                 // This is expected to not happen very often and hence should not be a
 | ||||||
|                 // performance problem.
 |                 // 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
 |             // Resize the texture in case the framebuffer size has changed
 | ||||||
|             textures[i].width = framebuffer.width; |             screen_infos[i].texture.width = framebuffer.width; | ||||||
|             textures[i].height = framebuffer.height; |             screen_infos[i].texture.height = framebuffer.height; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -166,8 +166,8 @@ void RendererOpenGL::SwapBuffers() { | ||||||
| /**
 | /**
 | ||||||
|  * Loads framebuffer from emulated memory into the active OpenGL texture. |  * Loads framebuffer from emulated memory into the active OpenGL texture. | ||||||
|  */ |  */ | ||||||
| void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, | void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, | ||||||
|                                              const TextureInfo& texture) { |                                          ScreenInfo& screen_info) { | ||||||
| 
 | 
 | ||||||
|     const PAddr framebuffer_addr = framebuffer.active_fb == 0 ? |     const PAddr framebuffer_addr = framebuffer.active_fb == 0 ? | ||||||
|             framebuffer.address_left1 : framebuffer.address_left2; |             framebuffer.address_left1 : framebuffer.address_left2; | ||||||
|  | @ -177,8 +177,6 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& | ||||||
|         framebuffer_addr, (int)framebuffer.width, |         framebuffer_addr, (int)framebuffer.width, | ||||||
|         (int)framebuffer.height, (int)framebuffer.format); |         (int)framebuffer.height, (int)framebuffer.format); | ||||||
| 
 | 
 | ||||||
|     const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); |  | ||||||
| 
 |  | ||||||
|     int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format); |     int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format); | ||||||
|     size_t pixel_stride = framebuffer.stride / bpp; |     size_t pixel_stride = framebuffer.stride / bpp; | ||||||
| 
 | 
 | ||||||
|  | @ -189,7 +187,16 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& | ||||||
|     // only allows rows to have a memory alignement of 4.
 |     // only allows rows to have a memory alignement of 4.
 | ||||||
|     ASSERT(pixel_stride % 4 == 0); |     ASSERT(pixel_stride % 4 == 0); | ||||||
| 
 | 
 | ||||||
|     state.texture_units[0].texture_2d = texture.handle; |     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); | ||||||
|  | 
 | ||||||
|  |         Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height); | ||||||
|  | 
 | ||||||
|  |         const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); | ||||||
|  | 
 | ||||||
|  |         state.texture_units[0].texture_2d = screen_info.texture.resource.handle; | ||||||
|         state.Apply(); |         state.Apply(); | ||||||
| 
 | 
 | ||||||
|         glActiveTexture(GL_TEXTURE0); |         glActiveTexture(GL_TEXTURE0); | ||||||
|  | @ -201,12 +208,13 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& | ||||||
|         // TODO: Applications could theoretically crash Citra here by specifying too large
 |         // TODO: Applications could theoretically crash Citra here by specifying too large
 | ||||||
|         //       framebuffer sizes. We should make sure that this cannot happen.
 |         //       framebuffer sizes. We should make sure that this cannot happen.
 | ||||||
|         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, |         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, | ||||||
|                     texture.gl_format, texture.gl_type, framebuffer_data); |                         screen_info.texture.gl_format, screen_info.texture.gl_type, framebuffer_data); | ||||||
| 
 | 
 | ||||||
|         glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |         glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||||||
| 
 | 
 | ||||||
|         state.texture_units[0].texture_2d = 0; |         state.texture_units[0].texture_2d = 0; | ||||||
|         state.Apply(); |         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, | void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, | ||||||
|                                                 const TextureInfo& texture) { |                                                 const TextureInfo& texture) { | ||||||
|     state.texture_units[0].texture_2d = texture.handle; |     state.texture_units[0].texture_2d = texture.resource.handle; | ||||||
|     state.Apply(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     glActiveTexture(GL_TEXTURE0); |     glActiveTexture(GL_TEXTURE0); | ||||||
|  | @ -224,6 +232,9 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color | ||||||
| 
 | 
 | ||||||
|     // Update existing texture
 |     // Update existing texture
 | ||||||
|     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data); |     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); |     glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f); | ||||||
| 
 | 
 | ||||||
|     // Link shaders and get variable locations
 |     // Link shaders and get variable locations
 | ||||||
|     program_id = GLShader::LoadProgram(vertex_shader, fragment_shader); |     shader.Create(vertex_shader, fragment_shader); | ||||||
|     uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix"); |     state.draw.shader_program = shader.handle; | ||||||
|     uniform_color_texture = glGetUniformLocation(program_id, "color_texture"); |     state.Apply(); | ||||||
|     attrib_position = glGetAttribLocation(program_id, "vert_position"); |     uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); | ||||||
|     attrib_tex_coord = glGetAttribLocation(program_id, "vert_tex_coord"); |     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
 |     // Generate VBO handle for drawing
 | ||||||
|     glGenBuffers(1, &vertex_buffer_handle); |     vertex_buffer.Create(); | ||||||
| 
 | 
 | ||||||
|     // Generate VAO
 |     // Generate VAO
 | ||||||
|     glGenVertexArrays(1, &vertex_array_handle); |     vertex_array.Create(); | ||||||
| 
 | 
 | ||||||
|     state.draw.vertex_array = vertex_array_handle; |     state.draw.vertex_array = vertex_array.handle; | ||||||
|     state.draw.vertex_buffer = vertex_buffer_handle; |     state.draw.vertex_buffer = vertex_buffer.handle; | ||||||
|     state.draw.uniform_buffer = 0; |     state.draw.uniform_buffer = 0; | ||||||
|     state.Apply(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|  | @ -258,13 +271,13 @@ void RendererOpenGL::InitOpenGLObjects() { | ||||||
|     glEnableVertexAttribArray(attrib_tex_coord); |     glEnableVertexAttribArray(attrib_tex_coord); | ||||||
| 
 | 
 | ||||||
|     // Allocate textures for each screen
 |     // Allocate textures for each screen
 | ||||||
|     for (auto& texture : textures) { |     for (auto& screen_info : screen_infos) { | ||||||
|         glGenTextures(1, &texture.handle); |         screen_info.texture.resource.Create(); | ||||||
| 
 | 
 | ||||||
|         // Allocation of storage is deferred until the first frame, when we
 |         // Allocation of storage is deferred until the first frame, when we
 | ||||||
|         // know the framebuffer size.
 |         // 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(); |         state.Apply(); | ||||||
| 
 | 
 | ||||||
|         glActiveTexture(GL_TEXTURE0); |         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_MIN_FILTER, GL_LINEAR); | ||||||
|         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |         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_WRAP_T, GL_CLAMP_TO_EDGE); | ||||||
|  | 
 | ||||||
|  |         screen_info.display_texture = screen_info.texture.resource.handle; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     state.texture_units[0].texture_2d = 0; |     state.texture_units[0].texture_2d = 0; | ||||||
|  | @ -327,30 +342,38 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, | ||||||
|         UNIMPLEMENTED(); |         UNIMPLEMENTED(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     state.texture_units[0].texture_2d = texture.handle; |     state.texture_units[0].texture_2d = texture.resource.handle; | ||||||
|     state.Apply(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     glActiveTexture(GL_TEXTURE0); |     glActiveTexture(GL_TEXTURE0); | ||||||
|     glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, |     glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, | ||||||
|             texture.gl_format, texture.gl_type, nullptr); |             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. |  * 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 = {{ |     std::array<ScreenRectVertex, 4> vertices = {{ | ||||||
|         ScreenRectVertex(x,   y,   1.f, 0.f), |         ScreenRectVertex(x,   y,   texcoords.bottom, texcoords.left), | ||||||
|         ScreenRectVertex(x+w, y,   1.f, 1.f), |         ScreenRectVertex(x+w, y,   texcoords.bottom, texcoords.right), | ||||||
|         ScreenRectVertex(x,   y+h, 0.f, 0.f), |         ScreenRectVertex(x,   y+h, texcoords.top, texcoords.left), | ||||||
|         ScreenRectVertex(x+w, y+h, 0.f, 1.f), |         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(); |     state.Apply(); | ||||||
| 
 | 
 | ||||||
|     glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); |     glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); | ||||||
|     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); |     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); |     glViewport(0, 0, layout.width, layout.height); | ||||||
|     glClear(GL_COLOR_BUFFER_BIT); |     glClear(GL_COLOR_BUFFER_BIT); | ||||||
| 
 | 
 | ||||||
|     state.draw.shader_program = program_id; |  | ||||||
|     state.Apply(); |  | ||||||
| 
 |  | ||||||
|     // Set projection matrix
 |     // Set projection matrix
 | ||||||
|     std::array<GLfloat, 3 * 2> ortho_matrix = MakeOrthographicMatrix((float)layout.width, |     std::array<GLfloat, 3 * 2> ortho_matrix = MakeOrthographicMatrix((float)layout.width, | ||||||
|         (float)layout.height); |         (float)layout.height); | ||||||
|  | @ -374,9 +394,9 @@ void RendererOpenGL::DrawScreens() { | ||||||
|     glActiveTexture(GL_TEXTURE0); |     glActiveTexture(GL_TEXTURE0); | ||||||
|     glUniform1i(uniform_color_texture, 0); |     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()); |         (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()); |         (float)layout.bottom_screen.GetWidth(), (float)layout.bottom_screen.GetHeight()); | ||||||
| 
 | 
 | ||||||
|     m_current_frame++; |     m_current_frame++; | ||||||
|  |  | ||||||
|  | @ -11,10 +11,28 @@ | ||||||
| #include "core/hw/gpu.h" | #include "core/hw/gpu.h" | ||||||
| 
 | 
 | ||||||
| #include "video_core/renderer_base.h" | #include "video_core/renderer_base.h" | ||||||
|  | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||||||
| #include "video_core/renderer_opengl/gl_state.h" | #include "video_core/renderer_opengl/gl_state.h" | ||||||
| 
 | 
 | ||||||
| class EmuWindow; | 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 { | class RendererOpenGL : public RendererBase { | ||||||
| public: | public: | ||||||
| 
 | 
 | ||||||
|  | @ -37,26 +55,16 @@ public: | ||||||
|     void ShutDown() override; |     void ShutDown() override; | ||||||
| 
 | 
 | ||||||
| private: | 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 InitOpenGLObjects(); | ||||||
|     void ConfigureFramebufferTexture(TextureInfo& texture, |     void ConfigureFramebufferTexture(TextureInfo& texture, | ||||||
|                                      const GPU::Regs::FramebufferConfig& framebuffer); |                                      const GPU::Regs::FramebufferConfig& framebuffer); | ||||||
|     void DrawScreens(); |     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(); |     void UpdateFramerate(); | ||||||
| 
 | 
 | ||||||
|     // Loads framebuffer from emulated memory into the active OpenGL texture.
 |     // Loads framebuffer from emulated memory into the display information structure
 | ||||||
|     void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, |     void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, | ||||||
|                                  const TextureInfo& texture); |                              ScreenInfo& screen_info); | ||||||
|     // Fills active OpenGL texture with the given RGB color.
 |     // Fills active OpenGL texture with the given RGB color.
 | ||||||
|     void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, |     void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, | ||||||
|                                     const TextureInfo& texture); |                                     const TextureInfo& texture); | ||||||
|  | @ -69,10 +77,10 @@ private: | ||||||
|     OpenGLState state; |     OpenGLState state; | ||||||
| 
 | 
 | ||||||
|     // OpenGL object IDs
 |     // OpenGL object IDs
 | ||||||
|     GLuint vertex_array_handle; |     OGLVertexArray vertex_array; | ||||||
|     GLuint vertex_buffer_handle; |     OGLBuffer vertex_buffer; | ||||||
|     GLuint program_id; |     OGLShader shader; | ||||||
|     std::array<TextureInfo, 2> textures;          ///< Textures for top and bottom screens respectively
 |     std::array<ScreenInfo, 2> screen_infos;          ///< Display information for top and bottom screens respectively
 | ||||||
|     // Shader uniform location indices
 |     // Shader uniform location indices
 | ||||||
|     GLuint uniform_modelview_matrix; |     GLuint uniform_modelview_matrix; | ||||||
|     GLuint uniform_color_texture; |     GLuint uniform_color_texture; | ||||||
|  |  | ||||||
|  | @ -11,16 +11,14 @@ | ||||||
| namespace VideoCore { | namespace VideoCore { | ||||||
| 
 | 
 | ||||||
| class SWRasterizer : public RasterizerInterface { | class SWRasterizer : public RasterizerInterface { | ||||||
|     void InitObjects() override {} |  | ||||||
|     void Reset() override {} |  | ||||||
|     void AddTriangle(const Pica::Shader::OutputVertex& v0, |     void AddTriangle(const Pica::Shader::OutputVertex& v0, | ||||||
|             const Pica::Shader::OutputVertex& v1, |             const Pica::Shader::OutputVertex& v1, | ||||||
|             const Pica::Shader::OutputVertex& v2) override; |             const Pica::Shader::OutputVertex& v2) override; | ||||||
|     void DrawTriangles() override {} |     void DrawTriangles() override {} | ||||||
|     void FlushFramebuffer() override {} |  | ||||||
|     void NotifyPicaRegisterChanged(u32 id) override {} |     void NotifyPicaRegisterChanged(u32 id) override {} | ||||||
|  |     void FlushAll() override {} | ||||||
|     void FlushRegion(PAddr addr, u32 size) override {} |     void FlushRegion(PAddr addr, u32 size) override {} | ||||||
|     void InvalidateRegion(PAddr addr, u32 size) override {} |     void FlushAndInvalidateRegion(PAddr addr, u32 size) override {} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue