diff options
Diffstat (limited to 'src/video_core')
39 files changed, 2302 insertions, 1452 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 76cfd4f7d..581a37897 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt @@ -15,7 +15,7 @@ set(SRCS shader/shader.cpp shader/shader_interpreter.cpp swrasterizer.cpp - utils.cpp + vertex_loader.cpp video_core.cpp ) @@ -43,6 +43,7 @@ set(HEADERS shader/shader_interpreter.h swrasterizer.h utils.h + vertex_loader.h video_core.h ) diff --git a/src/video_core/clipper.cpp b/src/video_core/clipper.cpp index 3d503486e..2bc747102 100644 --- a/src/video_core/clipper.cpp +++ b/src/video_core/clipper.cpp @@ -2,13 +2,24 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <array> +#include <cstddef> + #include <boost/container/static_vector.hpp> +#include <boost/container/vector.hpp> + +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/vector_math.h" #include "video_core/clipper.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/rasterizer.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" namespace Pica { diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 4b59984ad..dd1379503 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -2,26 +2,32 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cmath> -#include <boost/range/algorithm/fill.hpp> +#include <array> +#include <cstddef> +#include <memory> +#include <utility> -#include "common/alignment.h" +#include "common/assert.h" +#include "common/logging/log.h" #include "common/microprofile.h" -#include "common/profiler.h" +#include "common/vector_math.h" -#include "core/settings.h" #include "core/hle/service/gsp_gpu.h" #include "core/hw/gpu.h" +#include "core/memory.h" +#include "core/tracer/recorder.h" -#include "video_core/clipper.h" #include "video_core/command_processor.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/primitive_assembly.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" +#include "video_core/shader/shader.h" +#include "video_core/vertex_loader.h" #include "video_core/video_core.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/shader/shader_interpreter.h" namespace Pica { @@ -35,8 +41,6 @@ static int default_attr_counter = 0; static u32 default_attr_write_buffer[3]; -Common::Profiling::TimingCategory category_drawing("Drawing"); - // Expand a 4-bit mask to 4-byte mask, e.g. 0b0101 -> 0x00FF00FF static const u32 expand_bits_to_bytes[] = { 0x00000000, 0x000000ff, 0x0000ff00, 0x0000ffff, @@ -75,12 +79,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::P3D); break; + case PICA_REG_INDEX_WORKAROUND(triangle_topology, 0x25E): + g_state.primitive_assembler.Reconfigure(regs.triangle_topology); + break; + + case PICA_REG_INDEX_WORKAROUND(restart_primitive, 0x25F): + g_state.primitive_assembler.Reset(); + break; + case PICA_REG_INDEX_WORKAROUND(vs_default_attributes_setup.index, 0x232): - if (regs.vs_default_attributes_setup.index == 15) { - // Reset immediate primitive state - g_state.immediate.primitive_assembler.Reconfigure(regs.triangle_topology); - g_state.immediate.attribute_id = 0; - } + g_state.immediate.current_attribute = 0; + default_attr_counter = 0; break; // Load default vertex input attributes @@ -105,7 +114,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { break; } - Math::Vec4<float24>& attribute = g_state.vs.default_attributes[setup.index]; + Math::Vec4<float24> attribute; // NOTE: The destination component order indeed is "backwards" attribute.w = float24::FromRaw(default_attr_write_buffer[0] >> 8); @@ -119,26 +128,28 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { // TODO: Verify that this actually modifies the register! if (setup.index < 15) { + g_state.vs.default_attributes[setup.index] = attribute; setup.index++; } else { // Put each attribute into an immediate input buffer. // When all specified immediate attributes are present, the Vertex Shader is invoked and everything is // sent to the primitive assembler. - auto& immediate_input = g_state.immediate.input; - auto& immediate_attribute_id = g_state.immediate.attribute_id; - const auto& attribute_config = regs.vertex_attributes; + auto& immediate_input = g_state.immediate.input_vertex; + auto& immediate_attribute_id = g_state.immediate.current_attribute; immediate_input.attr[immediate_attribute_id++] = attribute; - if (immediate_attribute_id >= attribute_config.GetNumTotalAttributes()) { + if (immediate_attribute_id >= regs.vs.num_input_attributes+1) { immediate_attribute_id = 0; Shader::UnitState<false> shader_unit; - Shader::Setup(shader_unit); + Shader::Setup(); // Send to vertex shader - Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, attribute_config.GetNumTotalAttributes()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input)); + Shader::OutputVertex output = Shader::Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1); // Send to renderer using Pica::Shader::OutputVertex; @@ -146,7 +157,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); }; - g_state.immediate.primitive_assembler.SubmitVertex(output, AddTriangle); + g_state.primitive_assembler.SubmitVertex(output, AddTriangle); } } } @@ -154,9 +165,13 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } case PICA_REG_INDEX(gpu_mode): - if (regs.gpu_mode == Regs::GPUMode::Configuring && regs.vs_default_attributes_setup.index == 15) { + if (regs.gpu_mode == Regs::GPUMode::Configuring) { // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring VideoCore::g_renderer->Rasterizer()->DrawTriangles(); + + if (g_debug_context) { + g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); + } } break; @@ -174,60 +189,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { case PICA_REG_INDEX(trigger_draw): case PICA_REG_INDEX(trigger_draw_indexed): { - Common::Profiling::ScopeTimer scope_timer(category_drawing); MICROPROFILE_SCOPE(GPU_Drawing); #if PICA_LOG_TEV DebugUtils::DumpTevStageConfig(regs.GetTevStages()); #endif - if (g_debug_context) g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); - const auto& attribute_config = regs.vertex_attributes; - const u32 base_address = attribute_config.GetPhysicalBaseAddress(); - - // Information about internal vertex attributes - u32 vertex_attribute_sources[16]; - boost::fill(vertex_attribute_sources, 0xdeadbeef); - u32 vertex_attribute_strides[16] = {}; - Regs::VertexAttributeFormat vertex_attribute_formats[16] = {}; - - u32 vertex_attribute_elements[16] = {}; - u32 vertex_attribute_element_size[16] = {}; - - // Setup attribute data from loaders - for (int loader = 0; loader < 12; ++loader) { - const auto& loader_config = attribute_config.attribute_loaders[loader]; - - u32 offset = 0; - - // TODO: What happens if a loader overwrites a previous one's data? - for (unsigned component = 0; component < loader_config.component_count; ++component) { - if (component >= 12) { - LOG_ERROR(HW_GPU, "Overflow in the vertex attribute loader %u trying to load component %u", loader, component); - continue; - } - - u32 attribute_index = loader_config.GetComponent(component); - if (attribute_index < 12) { - int element_size = attribute_config.GetElementSizeInBytes(attribute_index); - offset = Common::AlignUp(offset, element_size); - vertex_attribute_sources[attribute_index] = base_address + loader_config.data_offset + offset; - vertex_attribute_strides[attribute_index] = static_cast<u32>(loader_config.byte_count); - vertex_attribute_formats[attribute_index] = attribute_config.GetFormat(attribute_index); - vertex_attribute_elements[attribute_index] = attribute_config.GetNumElements(attribute_index); - vertex_attribute_element_size[attribute_index] = element_size; - offset += attribute_config.GetStride(attribute_index); - } else if (attribute_index < 16) { - // Attribute ids 12, 13, 14 and 15 signify 4, 8, 12 and 16-byte paddings, respectively - offset = Common::AlignUp(offset, 4); - offset += (attribute_index - 11) * 4; - } else { - UNREACHABLE(); // This is truly unreachable due to the number of bits for each component - } - } - } + // Processes information about internal vertex attributes to figure out how a vertex is loaded. + // Later, these can be compiled and cached. + VertexLoader loader; + const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress(); + loader.Setup(regs); // Load vertices bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed)); @@ -237,11 +211,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); bool index_u16 = index_info.format != 0; -#if PICA_DUMP_GEOMETRY - DebugUtils::GeometryDumper geometry_dumper; - PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex> dumping_primitive_assembler(regs.triangle_topology.Value()); -#endif - PrimitiveAssembler<Shader::OutputVertex> primitive_assembler(regs.triangle_topology.Value()); + PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler; if (g_debug_context) { for (int i = 0; i < 3; ++i) { @@ -255,32 +225,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { } } - class { - /// Combine overlapping and close ranges - void SimplifyRanges() { - for (auto it = ranges.begin(); it != ranges.end(); ++it) { - // NOTE: We add 32 to the range end address to make sure "close" ranges are combined, too - auto it2 = std::next(it); - while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) { - it->second = std::max(it->second, it2->first + it2->second - it->first); - it2 = ranges.erase(it2); - } - } - } - - public: - /// Record a particular memory access in the list - void AddAccess(u32 paddr, u32 size) { - // Create new range or extend existing one - ranges[paddr] = std::max(ranges[paddr], size); - - // Simplify ranges... - SimplifyRanges(); - } - - /// Map of accessed ranges (mapping start address to range size) - std::map<u32, u32> ranges; - } memory_accesses; + DebugUtils::MemoryAccessTracker memory_accesses; // Simple circular-replacement vertex cache // The size has been tuned for optimal balance between hit-rate and the cost of lookup @@ -292,7 +237,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { vertex_cache_ids.fill(-1); Shader::UnitState<false> shader_unit; - Shader::Setup(shader_unit); + Shader::Setup(); for (unsigned int index = 0; index < regs.num_vertices; ++index) { @@ -324,71 +269,12 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { if (!vertex_cache_hit) { // Initialize data for the current vertex Shader::InputVertex input; + loader.LoadVertex(base_address, index, vertex, input, memory_accesses); - for (int i = 0; i < attribute_config.GetNumTotalAttributes(); ++i) { - if (vertex_attribute_elements[i] != 0) { - // Default attribute values set if array elements have < 4 components. This - // is *not* carried over from the default attribute settings even if they're - // enabled for this attribute. - static const float24 zero = float24::FromFloat32(0.0f); - static const float24 one = float24::FromFloat32(1.0f); - input.attr[i] = Math::Vec4<float24>(zero, zero, zero, one); - - // Load per-vertex data from the loader arrays - for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { - u32 source_addr = vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i]; - const u8* srcdata = Memory::GetPhysicalPointer(source_addr); - - if (g_debug_context && Pica::g_debug_context->recorder) { - memory_accesses.AddAccess(source_addr, - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 - : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1); - } - - const float srcval = - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::BYTE) ? *reinterpret_cast<const s8*>(srcdata) : - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::UBYTE) ? *reinterpret_cast<const u8*>(srcdata) : - (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? *reinterpret_cast<const s16*>(srcdata) : - *reinterpret_cast<const float*>(srcdata); - - input.attr[i][comp] = float24::FromFloat32(srcval); - LOG_TRACE(HW_GPU, "Loaded component %x of attribute %x for vertex %x (index %x) from 0x%08x + 0x%08x + 0x%04x: %f", - comp, i, vertex, index, - attribute_config.GetPhysicalBaseAddress(), - vertex_attribute_sources[i] - base_address, - vertex_attribute_strides[i] * vertex + comp * vertex_attribute_element_size[i], - input.attr[i][comp].ToFloat32()); - } - } else if (attribute_config.IsDefaultAttribute(i)) { - // Load the default attribute if we're configured to do so - input.attr[i] = g_state.vs.default_attributes[i]; - LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", - i, vertex, index, - input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), - input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); - } else { - // TODO(yuriks): In this case, no data gets loaded and the vertex - // remains with the last value it had. This isn't currently maintained - // as global state, however, and so won't work in Citra yet. - } - } - - if (g_debug_context) - g_debug_context->OnEvent(DebugContext::Event::VertexLoaded, (void*)&input); - -#if PICA_DUMP_GEOMETRY - // NOTE: When dumping geometry, we simply assume that the first input attribute - // corresponds to the position for now. - DebugUtils::GeometryDumper::Vertex dumped_vertex = { - input.attr[0][0].ToFloat32(), input.attr[0][1].ToFloat32(), input.attr[0][2].ToFloat32() - }; - using namespace std::placeholders; - dumping_primitive_assembler.SubmitVertex(dumped_vertex, - std::bind(&DebugUtils::GeometryDumper::AddTriangle, - &geometry_dumper, _1, _2, _3)); -#endif // Send to vertex shader - output = Shader::Run(shader_unit, input, attribute_config.GetNumTotalAttributes()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input); + output = Shader::Run(shader_unit, input, loader.GetNumTotalAttributes()); if (is_indexed) { vertex_cache[vertex_cache_pos] = output; @@ -412,16 +298,6 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { range.second, range.first); } - VideoCore::g_renderer->Rasterizer()->DrawTriangles(); - -#if PICA_DUMP_GEOMETRY - geometry_dumper.Dump(); -#endif - - if (g_debug_context) { - g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); - } - break; } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index bac6d69c7..fb20f81dd 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -4,35 +4,41 @@ #include <algorithm> #include <condition_variable> +#include <cstdint> #include <cstring> #include <fstream> -#include <list> #include <map> #include <mutex> +#include <stdexcept> #include <string> #ifdef HAVE_PNG #include <png.h> +#include <setjmp.h> #endif +#include <nihstro/bit_field.h> #include <nihstro/float24.h> #include <nihstro/shader_binary.h> #include "common/assert.h" +#include "common/bit_field.h" #include "common/color.h" #include "common/common_types.h" #include "common/file_util.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/vector_math.h" -#include "core/settings.h" - +#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_base.h" +#include "video_core/shader/shader.h" #include "video_core/utils.h" #include "video_core/video_core.h" -#include "video_core/debug_utils/debug_utils.h" using nihstro::DVLBHeader; using nihstro::DVLEHeader; @@ -40,15 +46,12 @@ using nihstro::DVLPHeader; namespace Pica { -void DebugContext::OnEvent(Event event, void* data) { - if (!breakpoints[event].enabled) - return; - +void DebugContext::DoOnEvent(Event event, void* data) { { std::unique_lock<std::mutex> lock(breakpoint_mutex); - // Commit the hardware renderer's framebuffer so it will show on debug widgets - VideoCore::g_renderer->Rasterizer()->FlushFramebuffer(); + // Commit the rasterizer's caches so framebuffers, render targets, etc. will show on debug widgets + VideoCore::g_renderer->Rasterizer()->FlushAll(); // TODO: Should stop the CPU thread here once we multithread emulation. @@ -85,35 +88,6 @@ std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global namespace DebugUtils { -void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { - vertices.push_back(v0); - vertices.push_back(v1); - vertices.push_back(v2); - - int num_vertices = (int)vertices.size(); - faces.push_back({{ num_vertices-3, num_vertices-2, num_vertices-1 }}); -} - -void GeometryDumper::Dump() { - static int index = 0; - std::string filename = std::string("geometry_dump") + std::to_string(++index) + ".obj"; - - std::ofstream file(filename); - - for (const auto& vertex : vertices) { - file << "v " << vertex.pos[0] - << " " << vertex.pos[1] - << " " << vertex.pos[2] << std::endl; - } - - for (const Face& face : faces) { - file << "f " << 1+face.index[0] - << " " << 1+face.index[1] - << " " << 1+face.index[2] << std::endl; - } -} - - void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, const Shader::ShaderSetup& setup, const Regs::VSOutputAttributes* output_attributes) { struct StuffToWrite { @@ -315,7 +289,7 @@ void StartPicaTracing() } std::lock_guard<std::mutex> lock(pica_trace_mutex); - pica_trace = std::unique_ptr<PicaTrace>(new PicaTrace); + pica_trace = std::make_unique<PicaTrace>(); is_pica_tracing = true; } @@ -615,6 +589,21 @@ TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, return info; } +#ifdef HAVE_PNG +// Adapter functions to libpng to write/flush to File::IOFile instances. +static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) { + auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); + if (!fp->WriteBytes(data, length)) + png_error(png_ptr, "Failed to write to output PNG file."); +} + +static void FlushIOFile(png_structp png_ptr) { + auto* fp = static_cast<FileUtil::IOFile*>(png_get_io_ptr(png_ptr)); + if (!fp->Flush()) + png_error(png_ptr, "Failed to flush to output PNG file."); +} +#endif + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { #ifndef HAVE_PNG return; @@ -658,7 +647,7 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { goto finalise; } - png_init_io(png_ptr, fp.GetHandle()); + png_set_write_fn(png_ptr, static_cast<void*>(&fp), WriteIOFile, FlushIOFile); // Write header (8 bit color depth) png_set_IHDR(png_ptr, info_ptr, texture_config.width, texture_config.height, diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index 795160a32..f628292a4 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -4,23 +4,33 @@ #pragma once +#include <algorithm> #include <array> #include <condition_variable> +#include <iterator> #include <list> #include <map> #include <memory> #include <mutex> +#include <string> +#include <utility> #include <vector> +#include "common/common_types.h" #include "common/vector_math.h" -#include "core/tracer/recorder.h" - #include "video_core/pica.h" -#include "video_core/shader/shader.h" + +namespace CiTrace { +class Recorder; +} namespace Pica { +namespace Shader { +struct ShaderSetup; +} + class DebugContext { public: enum class Event { @@ -30,7 +40,7 @@ public: PicaCommandProcessed, IncomingPrimitiveBatch, FinishedPrimitiveBatch, - VertexLoaded, + VertexShaderInvocation, IncomingDisplayTransfer, GSPCommandProcessed, BufferSwapped, @@ -114,7 +124,15 @@ public: * @param event Event which has happened * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. */ - void OnEvent(Event event, void* data); + void OnEvent(Event event, void* data) { + // This check is left in the header to allow the compiler to inline it. + if (!breakpoints[(int)event].enabled) + return; + // For the rest of event handling, call a separate function. + DoOnEvent(event, data); + } + + void DoOnEvent(Event event, void *data); /** * Resume from the current breakpoint. @@ -126,12 +144,14 @@ public: * Delete all set breakpoints and resume emulation. */ void ClearBreakpoints() { - breakpoints.clear(); + for (auto &bp : breakpoints) { + bp.enabled = false; + } Resume(); } // TODO: Evaluate if access to these members should be hidden behind a public interface. - std::map<Event, BreakPoint> breakpoints; + std::array<BreakPoint, (int)Event::NumEvents> breakpoints; Event active_breakpoint; bool at_breakpoint = false; @@ -158,30 +178,9 @@ extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this g namespace DebugUtils { -#define PICA_DUMP_GEOMETRY 0 #define PICA_DUMP_TEXTURES 0 #define PICA_LOG_TEV 0 -// Simple utility class for dumping geometry data to an OBJ file -class GeometryDumper { -public: - struct Vertex { - std::array<float,3> pos; - }; - - void AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2); - - void Dump(); - -private: - struct Face { - int index[3]; - }; - - std::vector<Vertex> vertices; - std::vector<Face> faces; -}; - void DumpShader(const std::string& filename, const Regs::ShaderConfig& config, const Shader::ShaderSetup& setup, const Regs::VSOutputAttributes* output_attributes); @@ -227,6 +226,36 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); +/** + * Used in the vertex loader to merge access records. TODO: Investigate if actually useful. + */ +class MemoryAccessTracker { + /// Combine overlapping and close ranges + void SimplifyRanges() { + for (auto it = ranges.begin(); it != ranges.end(); ++it) { + // NOTE: We add 32 to the range end address to make sure "close" ranges are combined, too + auto it2 = std::next(it); + while (it2 != ranges.end() && it->first + it->second + 32 >= it2->first) { + it->second = std::max(it->second, it2->first + it2->second - it->first); + it2 = ranges.erase(it2); + } + } + } + +public: + /// Record a particular memory access in the list + void AddAccess(u32 paddr, u32 size) { + // Create new range or extend existing one + ranges[paddr] = std::max(ranges[paddr], size); + + // Simplify ranges... + SimplifyRanges(); + } + + /// Map of accessed ranges (mapping start address to range size) + std::map<u32, u32> ranges; +}; + } // namespace } // namespace diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index 32ad72674..be82cf4b5 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp @@ -3,10 +3,13 @@ // Refer to the license.txt file included. #include <cstring> +#include <iterator> #include <unordered_map> +#include <utility> #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/primitive_assembly.h" #include "video_core/shader/shader.h" namespace Pica { @@ -480,7 +483,7 @@ std::string Regs::GetCommandName(int index) { static std::unordered_map<u32, const char*> map; if (map.empty()) { - map.insert(begin(register_names), end(register_names)); + map.insert(std::begin(register_names), std::end(register_names)); } // Return empty string if no match is found @@ -493,12 +496,25 @@ std::string Regs::GetCommandName(int index) { } void Init() { + g_state.Reset(); } void Shutdown() { Shader::Shutdown(); +} + +template <typename T> +void Zero(T& o) { + memset(&o, 0, sizeof(o)); +} - memset(&g_state, 0, sizeof(State)); +void State::Reset() { + Zero(regs); + Zero(vs); + Zero(gs); + Zero(cmd_list); + Zero(immediate); + primitive_assembler.Reconfigure(Regs::TriangleTopology::List); } } diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 337cff8ce..5891fb72a 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -5,10 +5,13 @@ #pragma once #include <array> -#include <cmath> #include <cstddef> #include <string> +#ifndef _MSC_VER +#include <type_traits> // for std::enable_if +#endif + #include "common/assert.h" #include "common/bit_field.h" #include "common/common_funcs.h" @@ -16,8 +19,6 @@ #include "common/vector_math.h" #include "common/logging/log.h" -#include "pica_types.h" - namespace Pica { // Returns index corresponding to the Regs member labeled by field_name @@ -71,7 +72,7 @@ struct Regs { BitField<0, 24, u32> viewport_depth_range; // float24 BitField<0, 24, u32> viewport_depth_far_plane; // float24 - INSERT_PADDING_WORDS(0x1); + BitField<0, 3, u32> vs_output_total; union VSOutputAttributes { // Maps components of output vertex attributes to semantics @@ -577,8 +578,18 @@ struct Regs { } } - struct { - INSERT_PADDING_WORDS(0x6); + struct FramebufferConfig { + INSERT_PADDING_WORDS(0x3); + + union { + BitField<0, 4, u32> allow_color_write; // 0 = disable, else enable + }; + + INSERT_PADDING_WORDS(0x1); + + union { + BitField<0, 2, u32> allow_depth_stencil_write; // 0 = disable, else enable + }; DepthFormat depth_format; // TODO: Should be a BitField! BitField<16, 3, ColorFormat> color_format; @@ -737,8 +748,13 @@ struct Regs { case LightingSampler::ReflectGreen: case LightingSampler::ReflectBlue: return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) || (config == LightingConfig::Config7); + default: + UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached " + "unreachable section, sampler should be one " + "of Distribution0, Distribution1, Fresnel, " + "ReflectRed, ReflectGreen or ReflectBlue, instead " + "got %i", static_cast<int>(config)); } - return false; } struct { @@ -1123,7 +1139,12 @@ struct Regs { BitField<24, 8, u32> w; } int_uniforms[4]; - INSERT_PADDING_WORDS(0x5); + INSERT_PADDING_WORDS(0x4); + + union { + // Number of input attributes to shader unit - 1 + BitField<0, 4, u32> num_input_attributes; + }; // Offset to shader program entry point (in words) BitField<0, 16, u32> main_offset; @@ -1157,8 +1178,10 @@ struct Regs { } } input_register_map; - // OUTMAP_MASK, 0x28E, CODETRANSFER_END - INSERT_PADDING_WORDS(0x3); + BitField<0, 16, u32> output_mask; + + // 0x28E, CODETRANSFER_END + INSERT_PADDING_WORDS(0x2); struct { enum Format : u32 diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index c7616bc55..bbecad850 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h @@ -4,6 +4,11 @@ #pragma once +#include <array> + +#include "common/bit_field.h" +#include "common/common_types.h" + #include "video_core/pica.h" #include "video_core/primitive_assembly.h" #include "video_core/shader/shader.h" @@ -12,6 +17,8 @@ namespace Pica { /// Struct used to describe current Pica state struct State { + void Reset(); + /// Pica registers Regs regs; @@ -46,13 +53,14 @@ struct State { /// Struct used to describe immediate mode rendering state struct ImmediateModeState { - Shader::InputVertex input; - // This is constructed with a dummy triangle topology - PrimitiveAssembler<Shader::OutputVertex> primitive_assembler; - int attribute_id = 0; - - ImmediateModeState() : primitive_assembler(Regs::TriangleTopology::List) {} + // Used to buffer partial vertices for immediate-mode rendering. + Shader::InputVertex input_vertex; + // Index of the next attribute to be loaded into `input_vertex`. + int current_attribute = 0; } immediate; + + // This is constructed with a dummy triangle topology + PrimitiveAssembler<Shader::OutputVertex> primitive_assembler; }; extern State g_state; ///< Current Pica state diff --git a/src/video_core/pica_types.h b/src/video_core/pica_types.h index ecf45654b..3b7bfbdca 100644 --- a/src/video_core/pica_types.h +++ b/src/video_core/pica_types.h @@ -4,6 +4,7 @@ #pragma once +#include <cmath> #include <cstring> #include "common/common_types.h" diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index 0061690f1..68ea3c08a 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp @@ -6,8 +6,7 @@ #include "video_core/pica.h" #include "video_core/primitive_assembly.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" namespace Pica { @@ -68,7 +67,5 @@ void PrimitiveAssembler<VertexType>::Reconfigure(Regs::TriangleTopology topology // explicitly instantiate use cases template struct PrimitiveAssembler<Shader::OutputVertex>; -template -struct PrimitiveAssembler<DebugUtils::GeometryDumper::Vertex>; } // namespace diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index cc6e5fde5..9396b4c85 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h @@ -20,7 +20,7 @@ struct PrimitiveAssembler { VertexType& v1, VertexType& v2)>; - PrimitiveAssembler(Regs::TriangleTopology topology); + PrimitiveAssembler(Regs::TriangleTopology topology = Regs::TriangleTopology::List); /* * Queues a vertex, builds primitives from the vertex queue according to the given diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp index fd02aa652..df67b9081 100644 --- a/src/video_core/rasterizer.cpp +++ b/src/video_core/rasterizer.cpp @@ -3,23 +3,28 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <cmath> +#include "common/assert.h" +#include "common/bit_field.h" #include "common/color.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/microprofile.h" -#include "common/profiler.h" +#include "common/vector_math.h" #include "core/memory.h" #include "core/hw/gpu.h" +#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/rasterizer.h" #include "video_core/utils.h" -#include "video_core/debug_utils/debug_utils.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" namespace Pica { @@ -287,7 +292,6 @@ static int SignedArea (const Math::Vec2<Fix12P4>& vtx1, return Math::Cross(vec1, vec2).z; }; -static Common::Profiling::TimingCategory rasterization_category("Rasterization"); MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 240)); /** @@ -300,7 +304,6 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, bool reversed = false) { const auto& regs = g_state.regs; - Common::Profiling::ScopeTimer timer(rasterization_category); MICROPROFILE_SCOPE(GPU_Rasterization); // vertex positions in rasterizer coordinates @@ -809,7 +812,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, auto UpdateStencil = [stencil_test, x, y, &old_stencil](Pica::Regs::StencilAction action) { u8 new_stencil = PerformStencilAction(action, old_stencil, stencil_test.reference_value); - SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) | (old_stencil & ~stencil_test.write_mask)); + if (g_state.regs.framebuffer.allow_depth_stencil_write != 0) + SetStencil(x >> 4, y >> 4, (new_stencil & stencil_test.write_mask) | (old_stencil & ~stencil_test.write_mask)); }; if (stencil_action_enable) { @@ -909,7 +913,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, } } - if (output_merger.depth_write_enable) + if (regs.framebuffer.allow_depth_stencil_write != 0 && output_merger.depth_write_enable) SetDepth(x >> 4, y >> 4, z); // The stencil depth_pass action is executed even if depth testing is disabled @@ -922,92 +926,72 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, if (output_merger.alphablend_enable) { auto params = output_merger.alpha_blending; - auto LookupFactorRGB = [&](Regs::BlendFactor factor) -> Math::Vec3<u8> { + auto LookupFactor = [&](unsigned channel, Regs::BlendFactor factor) -> u8 { + DEBUG_ASSERT(channel < 4); + + const Math::Vec4<u8> blend_const = { + static_cast<u8>(output_merger.blend_const.r), + static_cast<u8>(output_merger.blend_const.g), + static_cast<u8>(output_merger.blend_const.b), + static_cast<u8>(output_merger.blend_const.a) + }; + switch (factor) { - case Regs::BlendFactor::Zero : - return Math::Vec3<u8>(0, 0, 0); + case Regs::BlendFactor::Zero: + return 0; - case Regs::BlendFactor::One : - return Math::Vec3<u8>(255, 255, 255); + case Regs::BlendFactor::One: + return 255; case Regs::BlendFactor::SourceColor: - return combiner_output.rgb(); + return combiner_output[channel]; case Regs::BlendFactor::OneMinusSourceColor: - return Math::Vec3<u8>(255 - combiner_output.r(), 255 - combiner_output.g(), 255 - combiner_output.b()); + return 255 - combiner_output[channel]; case Regs::BlendFactor::DestColor: - return dest.rgb(); + return dest[channel]; case Regs::BlendFactor::OneMinusDestColor: - return Math::Vec3<u8>(255 - dest.r(), 255 - dest.g(), 255 - dest.b()); + return 255 - dest[channel]; case Regs::BlendFactor::SourceAlpha: - return Math::Vec3<u8>(combiner_output.a(), combiner_output.a(), combiner_output.a()); + return combiner_output.a(); case Regs::BlendFactor::OneMinusSourceAlpha: - return Math::Vec3<u8>(255 - combiner_output.a(), 255 - combiner_output.a(), 255 - combiner_output.a()); + return 255 - combiner_output.a(); case Regs::BlendFactor::DestAlpha: - return Math::Vec3<u8>(dest.a(), dest.a(), dest.a()); + return dest.a(); case Regs::BlendFactor::OneMinusDestAlpha: - return Math::Vec3<u8>(255 - dest.a(), 255 - dest.a(), 255 - dest.a()); + return 255 - dest.a(); case Regs::BlendFactor::ConstantColor: - return Math::Vec3<u8>(output_merger.blend_const.r, output_merger.blend_const.g, output_merger.blend_const.b); + return blend_const[channel]; case Regs::BlendFactor::OneMinusConstantColor: - return Math::Vec3<u8>(255 - output_merger.blend_const.r, 255 - output_merger.blend_const.g, 255 - output_merger.blend_const.b); + return 255 - blend_const[channel]; case Regs::BlendFactor::ConstantAlpha: - return Math::Vec3<u8>(output_merger.blend_const.a, output_merger.blend_const.a, output_merger.blend_const.a); + return blend_const.a(); case Regs::BlendFactor::OneMinusConstantAlpha: - return Math::Vec3<u8>(255 - output_merger.blend_const.a, 255 - output_merger.blend_const.a, 255 - output_merger.blend_const.a); - - default: - LOG_CRITICAL(HW_GPU, "Unknown color blend factor %x", factor); - UNIMPLEMENTED(); - break; - } - - return {}; - }; - - auto LookupFactorA = [&](Regs::BlendFactor factor) -> u8 { - switch (factor) { - case Regs::BlendFactor::Zero: - return 0; + return 255 - blend_const.a(); - case Regs::BlendFactor::One: - return 255; - - case Regs::BlendFactor::SourceAlpha: - return combiner_output.a(); - - case Regs::BlendFactor::OneMinusSourceAlpha: - return 255 - combiner_output.a(); - - case Regs::BlendFactor::DestAlpha: - return dest.a(); - - case Regs::BlendFactor::OneMinusDestAlpha: - return 255 - dest.a(); - - case Regs::BlendFactor::ConstantAlpha: - return output_merger.blend_const.a; - - case Regs::BlendFactor::OneMinusConstantAlpha: - return 255 - output_merger.blend_const.a; + case Regs::BlendFactor::SourceAlphaSaturate: + // Returns 1.0 for the alpha channel + if (channel == 3) + return 255; + return std::min(combiner_output.a(), static_cast<u8>(255 - dest.a())); default: - LOG_CRITICAL(HW_GPU, "Unknown alpha blend factor %x", factor); + LOG_CRITICAL(HW_GPU, "Unknown blend factor %x", factor); UNIMPLEMENTED(); break; } - return {}; + return combiner_output[channel]; }; static auto EvaluateBlendEquation = [](const Math::Vec4<u8>& src, const Math::Vec4<u8>& srcfactor, @@ -1059,10 +1043,15 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, MathUtil::Clamp(result.a(), 0, 255)); }; - auto srcfactor = Math::MakeVec(LookupFactorRGB(params.factor_source_rgb), - LookupFactorA(params.factor_source_a)); - auto dstfactor = Math::MakeVec(LookupFactorRGB(params.factor_dest_rgb), - LookupFactorA(params.factor_dest_a)); + auto srcfactor = Math::MakeVec(LookupFactor(0, params.factor_source_rgb), + LookupFactor(1, params.factor_source_rgb), + LookupFactor(2, params.factor_source_rgb), + LookupFactor(3, params.factor_source_a)); + + auto dstfactor = Math::MakeVec(LookupFactor(0, params.factor_dest_rgb), + LookupFactor(1, params.factor_dest_rgb), + LookupFactor(2, params.factor_dest_rgb), + LookupFactor(3, params.factor_dest_a)); blend_output = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_rgb); blend_output.a() = EvaluateBlendEquation(combiner_output, srcfactor, dest, dstfactor, params.blend_equation_a).a(); @@ -1133,7 +1122,8 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, output_merger.alpha_enable ? blend_output.a() : dest.a() }; - DrawPixel(x >> 4, y >> 4, result); + if (regs.framebuffer.allow_color_write != 0) + DrawPixel(x >> 4, y >> 4, result); } } } diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 008c5827b..bf7101665 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h @@ -6,6 +6,10 @@ #include "common/common_types.h" +#include "core/hw/gpu.h" + +struct ScreenInfo; + namespace Pica { namespace Shader { struct OutputVertex; @@ -18,12 +22,6 @@ class RasterizerInterface { public: virtual ~RasterizerInterface() {} - /// Initialize API-specific GPU objects - virtual void InitObjects() = 0; - - /// Reset the rasterizer, such as flushing all caches and updating all state - virtual void Reset() = 0; - /// Queues the primitive formed by the given vertices for rendering virtual void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, @@ -32,17 +30,26 @@ public: /// Draw the current batch of triangles virtual void DrawTriangles() = 0; - /// Commit the rasterizer's framebuffer contents immediately to the current 3DS memory framebuffer - virtual void FlushFramebuffer() = 0; - /// Notify rasterizer that the specified PICA register has been changed virtual void NotifyPicaRegisterChanged(u32 id) = 0; - /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory. + /// Notify rasterizer that all caches should be flushed to 3DS memory + virtual void FlushAll() = 0; + + /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory virtual void FlushRegion(PAddr addr, u32 size) = 0; - /// Notify rasterizer that any caches of the specified region should be discraded and reloaded from 3DS memory. - virtual void InvalidateRegion(PAddr addr, u32 size) = 0; + /// Notify rasterizer that any caches of the specified region should be flushed to 3DS memory and invalidated + virtual void FlushAndInvalidateRegion(PAddr addr, u32 size) = 0; + + /// Attempt to use a faster method to perform a display transfer + virtual bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { return false; } + + /// Attempt to use a faster method to fill a region + virtual bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { return false; } + + /// Attempt to use a faster method to display the framebuffer to screen + virtual bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { return false; } }; } diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp index 6467ff723..3f451e062 100644 --- a/src/video_core/renderer_base.cpp +++ b/src/video_core/renderer_base.cpp @@ -2,12 +2,9 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> #include <memory> -#include "common/make_unique.h" - -#include "core/settings.h" - #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "video_core/swrasterizer.h" @@ -19,11 +16,9 @@ void RendererBase::RefreshRasterizerSetting() { opengl_rasterizer_active = hw_renderer_enabled; if (hw_renderer_enabled) { - rasterizer = Common::make_unique<RasterizerOpenGL>(); + rasterizer = std::make_unique<RasterizerOpenGL>(); } else { - rasterizer = Common::make_unique<VideoCore::SWRasterizer>(); + rasterizer = std::make_unique<VideoCore::SWRasterizer>(); } - rasterizer->InitObjects(); - rasterizer->Reset(); } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index b3dc6aa19..519d81aeb 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -2,29 +2,28 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> #include <memory> +#include <string> +#include <tuple> +#include <utility> #include <glad/glad.h> +#include "common/assert.h" #include "common/color.h" -#include "common/file_util.h" -#include "common/make_unique.h" +#include "common/logging/log.h" #include "common/math_util.h" -#include "common/microprofile.h" -#include "common/profiler.h" +#include "common/vector_math.h" -#include "core/memory.h" -#include "core/settings.h" #include "core/hw/gpu.h" #include "video_core/pica.h" #include "video_core/pica_state.h" -#include "video_core/utils.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" #include "video_core/renderer_opengl/gl_shader_util.h" #include "video_core/renderer_opengl/pica_to_gl.h" +#include "video_core/renderer_opengl/renderer_opengl.h" static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { return (stage.color_op == Pica::Regs::TevStageConfig::Operation::Replace && @@ -37,10 +36,7 @@ static bool IsPassThroughTevStage(const Pica::Regs::TevStageConfig& stage) { stage.GetAlphaMultiplier() == 1); } -RasterizerOpenGL::RasterizerOpenGL() : cached_fb_color_addr(0), cached_fb_depth_addr(0) { } -RasterizerOpenGL::~RasterizerOpenGL() { } - -void RasterizerOpenGL::InitObjects() { +RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { // Create sampler objects for (size_t i = 0; i < texture_samplers.size(); ++i) { texture_samplers[i].Create(); @@ -62,6 +58,10 @@ void RasterizerOpenGL::InitObjects() { uniform_block_data.dirty = true; + for (unsigned index = 0; index < lighting_luts.size(); index++) { + uniform_block_data.lut_dirty[index] = true; + } + // Set vertex attributes glVertexAttribPointer(GLShader::ATTRIBUTE_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, position)); glEnableVertexAttribArray(GLShader::ATTRIBUTE_POSITION); @@ -82,69 +82,24 @@ void RasterizerOpenGL::InitObjects() { glVertexAttribPointer(GLShader::ATTRIBUTE_VIEW, 3, GL_FLOAT, GL_FALSE, sizeof(HardwareVertex), (GLvoid*)offsetof(HardwareVertex, view)); glEnableVertexAttribArray(GLShader::ATTRIBUTE_VIEW); - SetShader(); - - // Create textures for OGL framebuffer that will be rendered to, initially 1x1 to succeed in framebuffer creation - fb_color_texture.texture.Create(); - ReconfigureColorTexture(fb_color_texture, Pica::Regs::ColorFormat::RGBA8, 1, 1); - - state.texture_units[0].texture_2d = fb_color_texture.texture.handle; - state.Apply(); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - fb_depth_texture.texture.Create(); - ReconfigureDepthTexture(fb_depth_texture, Pica::Regs::DepthFormat::D16, 1, 1); - - state.texture_units[0].texture_2d = fb_depth_texture.texture.handle; - state.Apply(); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - // Configure OpenGL framebuffer + // Create render framebuffer framebuffer.Create(); - state.draw.framebuffer = framebuffer.handle; + // Allocate and bind lighting lut textures + for (size_t i = 0; i < lighting_luts.size(); ++i) { + lighting_luts[i].Create(); + state.lighting_luts[i].texture_1d = lighting_luts[i].handle; + } state.Apply(); - glActiveTexture(GL_TEXTURE0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb_color_texture.texture.handle, 0); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fb_depth_texture.texture.handle, 0); - - for (size_t i = 0; i < lighting_lut.size(); ++i) { - lighting_lut[i].Create(); - state.lighting_lut[i].texture_1d = lighting_lut[i].handle; - + for (size_t i = 0; i < lighting_luts.size(); ++i) { glActiveTexture(GL_TEXTURE3 + i); - glBindTexture(GL_TEXTURE_1D, state.lighting_lut[i].texture_1d); - glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } - state.Apply(); - ASSERT_MSG(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, - "OpenGL rasterizer framebuffer setup failed, status %X", glCheckFramebufferStatus(GL_FRAMEBUFFER)); -} - -void RasterizerOpenGL::Reset() { + // Sync fixed function OpenGL state SyncCullMode(); SyncDepthModifiers(); SyncBlendEnabled(); @@ -153,10 +108,13 @@ void RasterizerOpenGL::Reset() { SyncLogicOp(); SyncStencilTest(); SyncDepthTest(); + SyncColorWriteMask(); + SyncStencilWriteMask(); + SyncDepthWriteMask(); +} - SetShader(); +RasterizerOpenGL::~RasterizerOpenGL() { - res_cache.InvalidateAll(); } /** @@ -190,47 +148,101 @@ void RasterizerOpenGL::AddTriangle(const Pica::Shader::OutputVertex& v0, } void RasterizerOpenGL::DrawTriangles() { - SyncFramebuffer(); - SyncDrawState(); + if (vertex_batch.empty()) + return; + + const auto& regs = Pica::g_state.regs; + + // Sync and bind the framebuffer surfaces + CachedSurface* color_surface; + CachedSurface* depth_surface; + MathUtil::Rectangle<int> rect; + std::tie(color_surface, depth_surface, rect) = res_cache.GetFramebufferSurfaces(regs.framebuffer); + + state.draw.draw_framebuffer = framebuffer.handle; + state.Apply(); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_surface != nullptr ? color_surface->texture.handle : 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_surface != nullptr ? depth_surface->texture.handle : 0, 0); + bool has_stencil = regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, (has_stencil && depth_surface != nullptr) ? depth_surface->texture.handle : 0, 0); + + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return; + } + + // Sync the viewport + // These registers hold half-width and half-height, so must be multiplied by 2 + GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; + GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; + + glViewport((GLint)(rect.left + regs.viewport_corner.x * color_surface->res_scale_width), + (GLint)(rect.bottom + regs.viewport_corner.y * color_surface->res_scale_height), + (GLsizei)(viewport_width * color_surface->res_scale_width), (GLsizei)(viewport_height * color_surface->res_scale_height)); + + // Sync and bind the texture surfaces + const auto pica_textures = regs.GetTextures(); + for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { + const auto& texture = pica_textures[texture_index]; - if (state.draw.shader_dirty) { + 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(); - state.draw.shader_dirty = false; + shader_dirty = false; } - for (unsigned index = 0; index < lighting_lut.size(); index++) { + // Sync the lighting luts + for (unsigned index = 0; index < lighting_luts.size(); index++) { if (uniform_block_data.lut_dirty[index]) { SyncLightingLUT(index); uniform_block_data.lut_dirty[index] = false; } } + // Sync the uniform data if (uniform_block_data.dirty) { glBufferData(GL_UNIFORM_BUFFER, sizeof(UniformData), &uniform_block_data.data, GL_STATIC_DRAW); uniform_block_data.dirty = false; } + state.Apply(); + + // Draw the vertex batch glBufferData(GL_ARRAY_BUFFER, vertex_batch.size() * sizeof(HardwareVertex), vertex_batch.data(), GL_STREAM_DRAW); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)vertex_batch.size()); - vertex_batch.clear(); - - // Flush the resource cache at the current depth and color framebuffer addresses for render-to-texture - const auto& regs = Pica::g_state.regs; - - u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) - * fb_color_texture.width * fb_color_texture.height; - - u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) - * fb_depth_texture.width * fb_depth_texture.height; + // Mark framebuffer surfaces as dirty + // TODO: Restrict invalidation area to the viewport + if (color_surface != nullptr) { + color_surface->dirty = true; + res_cache.FlushRegion(color_surface->addr, color_surface->size, color_surface, true); + } + if (depth_surface != nullptr) { + depth_surface->dirty = true; + res_cache.FlushRegion(depth_surface->addr, depth_surface->size, depth_surface, true); + } - res_cache.InvalidateInRange(cached_fb_color_addr, cached_fb_color_size, true); - res_cache.InvalidateInRange(cached_fb_depth_addr, cached_fb_depth_size, true); -} + vertex_batch.clear(); -void RasterizerOpenGL::FlushFramebuffer() { - CommitColorBuffer(); - CommitDepthBuffer(); + // Unbind textures for potential future use as framebuffer attachments + for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { + state.texture_units[texture_index].texture_2d = 0; + } + state.Apply(); } void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { @@ -262,18 +274,39 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { // Alpha test case PICA_REG_INDEX(output_merger.alpha_test): SyncAlphaTest(); - state.draw.shader_dirty = true; + shader_dirty = true; break; - // Stencil test + // Sync GL stencil test + stencil write mask + // (Pica stencil test function register also contains a stencil write mask) case PICA_REG_INDEX(output_merger.stencil_test.raw_func): + SyncStencilTest(); + SyncStencilWriteMask(); + break; case PICA_REG_INDEX(output_merger.stencil_test.raw_op): + case PICA_REG_INDEX(framebuffer.depth_format): SyncStencilTest(); break; - // Depth test + // Sync GL depth test + depth and color write mask + // (Pica depth test function register also contains a depth and color write mask) case PICA_REG_INDEX(output_merger.depth_test_enable): SyncDepthTest(); + SyncDepthWriteMask(); + SyncColorWriteMask(); + break; + + // Sync GL depth and stencil write mask + // (This is a dedicated combined depth / stencil write-enable register) + case PICA_REG_INDEX(framebuffer.allow_depth_stencil_write): + SyncDepthWriteMask(); + SyncStencilWriteMask(); + break; + + // Sync GL color write mask + // (This is a dedicated color write-enable register) + case PICA_REG_INDEX(framebuffer.allow_color_write): + SyncColorWriteMask(); break; // Logic op @@ -307,7 +340,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { case PICA_REG_INDEX(tev_stage5.color_op): case PICA_REG_INDEX(tev_stage5.color_scale): case PICA_REG_INDEX(tev_combiner_buffer_input): - state.draw.shader_dirty = true; + shader_dirty = true; break; case PICA_REG_INDEX(tev_stage0.const_r): SyncTevConstColor(0, regs.tev_stage0); @@ -494,41 +527,257 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { } } +void RasterizerOpenGL::FlushAll() { + res_cache.FlushAll(); +} + void RasterizerOpenGL::FlushRegion(PAddr addr, u32 size) { - const auto& regs = Pica::g_state.regs; + res_cache.FlushRegion(addr, size, nullptr, false); +} + +void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) { + res_cache.FlushRegion(addr, size, nullptr, true); +} + +bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) { + using PixelFormat = CachedSurface::PixelFormat; + using SurfaceType = CachedSurface::SurfaceType; - u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) - * fb_color_texture.width * fb_color_texture.height; + if (config.is_texture_copy) { + // TODO(tfarley): Try to hardware accelerate this + return false; + } + + CachedSurface src_params; + src_params.addr = config.GetPhysicalInputAddress(); + src_params.width = config.output_width; + src_params.height = config.output_height; + src_params.is_tiled = !config.input_linear; + src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.input_format); + + CachedSurface dst_params; + dst_params.addr = config.GetPhysicalOutputAddress(); + dst_params.width = config.scaling != config.NoScale ? config.output_width / 2 : config.output_width.Value(); + dst_params.height = config.scaling == config.ScaleXY ? config.output_height / 2 : config.output_height.Value(); + dst_params.is_tiled = config.input_linear != config.dont_swizzle; + dst_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.output_format); + + MathUtil::Rectangle<int> src_rect; + CachedSurface* src_surface = res_cache.GetSurfaceRect(src_params, false, true, src_rect); + + if (src_surface == nullptr) { + return false; + } - u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) - * fb_depth_texture.width * fb_depth_texture.height; + // 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; - // 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(); + MathUtil::Rectangle<int> dst_rect; + CachedSurface* dst_surface = res_cache.GetSurfaceRect(dst_params, true, false, dst_rect); + + if (dst_surface == nullptr) { + return false; + } - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) - CommitDepthBuffer(); + // Don't accelerate if the src and dst surfaces are the same + if (src_surface == dst_surface) { + return false; + } + + if (config.flip_vertically) { + std::swap(dst_rect.top, dst_rect.bottom); + } + + if (!res_cache.TryBlitSurfaces(src_surface, src_rect, dst_surface, dst_rect)) { + return false; + } + + u32 dst_size = dst_params.width * dst_params.height * CachedSurface::GetFormatBpp(dst_params.pixel_format) / 8; + dst_surface->dirty = true; + res_cache.FlushRegion(config.GetPhysicalOutputAddress(), dst_size, dst_surface, true); + return true; } -void RasterizerOpenGL::InvalidateRegion(PAddr addr, u32 size) { - const auto& regs = Pica::g_state.regs; +bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) { + using PixelFormat = CachedSurface::PixelFormat; + using SurfaceType = CachedSurface::SurfaceType; + + CachedSurface* dst_surface = res_cache.TryGetFillSurface(config); + + if (dst_surface == nullptr) { + return false; + } + + OpenGLState cur_state = OpenGLState::GetCurState(); + + SurfaceType dst_type = CachedSurface::GetFormatType(dst_surface->pixel_format); + + 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); - u32 cached_fb_color_size = Pica::Regs::BytesPerColorPixel(fb_color_texture.format) - * fb_color_texture.width * fb_color_texture.height; + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } - u32 cached_fb_depth_size = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format) - * fb_depth_texture.width * fb_depth_texture.height; + GLfloat value_float = (config.value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 + GLint value_int = (config.value_32bit >> 24); - // If modified memory region overlaps 3DS framebuffers, reload their contents into OpenGL - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_color_addr, cached_fb_color_size)) - ReloadColorBuffer(); + cur_state.depth.write_mask = true; + cur_state.stencil.write_mask = true; + cur_state.Apply(); + glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int); + } - if (MathUtil::IntervalsIntersect(addr, size, cached_fb_depth_addr, cached_fb_depth_size)) - ReloadDepthBuffer(); + cur_state.draw.draw_framebuffer = old_fb; + // TODO: Return scissor test to previous value when scissor test is implemented + cur_state.Apply(); - // Notify cache of flush in case the region touches a cached resource - res_cache.InvalidateInRange(addr, size); + dst_surface->dirty = true; + res_cache.FlushRegion(dst_surface->addr, dst_surface->size, dst_surface, true); + return true; +} + +bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) { + if (framebuffer_addr == 0) { + return false; + } + + CachedSurface src_params; + src_params.addr = framebuffer_addr; + src_params.width = config.width; + src_params.height = config.height; + src_params.stride = pixel_stride; + src_params.is_tiled = false; + src_params.pixel_format = CachedSurface::PixelFormatFromGPUPixelFormat(config.color_format); + + MathUtil::Rectangle<int> src_rect; + CachedSurface* src_surface = res_cache.GetSurfaceRect(src_params, false, true, src_rect); + + if (src_surface == nullptr) { + return false; + } + + u32 scaled_width = src_surface->GetScaledWidth(); + u32 scaled_height = src_surface->GetScaledHeight(); + + screen_info.display_texcoords = MathUtil::Rectangle<float>((float)src_rect.top / (float)scaled_height, + (float)src_rect.left / (float)scaled_width, + (float)src_rect.bottom / (float)scaled_height, + (float)src_rect.right / (float)scaled_width); + + screen_info.display_texture = src_surface->texture.handle; + + return true; } void RasterizerOpenGL::SamplerInfo::Create() { @@ -564,117 +813,16 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Pica::Regs::TextureConf if (wrap_s == TextureConfig::ClampToBorder || wrap_t == TextureConfig::ClampToBorder) { if (border_color != config.border_color.raw) { + border_color = config.border_color.raw; auto gl_color = PicaToGL::ColorRGBA8(border_color); glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.data()); } } } -void RasterizerOpenGL::ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height) { - GLint internal_format; - - texture.format = format; - texture.width = width; - texture.height = height; - - switch (format) { - case Pica::Regs::ColorFormat::RGBA8: - internal_format = GL_RGBA; - texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_INT_8_8_8_8; - break; - - case Pica::Regs::ColorFormat::RGB8: - // This pixel format uses BGR since GL_UNSIGNED_BYTE specifies byte-order, unlike every - // specific OpenGL type used in this function using native-endian (that is, little-endian - // mostly everywhere) for words or half-words. - // TODO: check how those behave on big-endian processors. - internal_format = GL_RGB; - texture.gl_format = GL_BGR; - texture.gl_type = GL_UNSIGNED_BYTE; - break; - - case Pica::Regs::ColorFormat::RGB5A1: - internal_format = GL_RGBA; - texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_SHORT_5_5_5_1; - break; - - case Pica::Regs::ColorFormat::RGB565: - internal_format = GL_RGB; - texture.gl_format = GL_RGB; - texture.gl_type = GL_UNSIGNED_SHORT_5_6_5; - break; - - case Pica::Regs::ColorFormat::RGBA4: - internal_format = GL_RGBA; - texture.gl_format = GL_RGBA; - texture.gl_type = GL_UNSIGNED_SHORT_4_4_4_4; - break; - - default: - LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer texture color format %x", format); - UNIMPLEMENTED(); - break; - } - - state.texture_units[0].texture_2d = texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, - texture.gl_format, texture.gl_type, nullptr); - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - -void RasterizerOpenGL::ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height) { - GLint internal_format; - - texture.format = format; - texture.width = width; - texture.height = height; - - switch (format) { - case Pica::Regs::DepthFormat::D16: - internal_format = GL_DEPTH_COMPONENT16; - texture.gl_format = GL_DEPTH_COMPONENT; - texture.gl_type = GL_UNSIGNED_SHORT; - break; - - case Pica::Regs::DepthFormat::D24: - internal_format = GL_DEPTH_COMPONENT24; - texture.gl_format = GL_DEPTH_COMPONENT; - texture.gl_type = GL_UNSIGNED_INT; - break; - - case Pica::Regs::DepthFormat::D24S8: - internal_format = GL_DEPTH24_STENCIL8; - texture.gl_format = GL_DEPTH_STENCIL; - texture.gl_type = GL_UNSIGNED_INT_24_8; - break; - - default: - LOG_CRITICAL(Render_OpenGL, "Unknown framebuffer texture depth format %x", format); - UNIMPLEMENTED(); - break; - } - - state.texture_units[0].texture_2d = texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, - texture.gl_format, texture.gl_type, nullptr); - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - void RasterizerOpenGL::SetShader() { PicaShaderConfig config = PicaShaderConfig::CurrentConfig(); - std::unique_ptr<PicaShader> shader = Common::make_unique<PicaShader>(); + std::unique_ptr<PicaShader> shader = std::make_unique<PicaShader>(); // Find (or generate) the GLSL shader for the current TEV state auto cached_shader = shader_cache.find(config); @@ -727,6 +875,8 @@ void RasterizerOpenGL::SetShader() { SyncGlobalAmbient(); for (int light_index = 0; light_index < 8; light_index++) { + SyncLightSpecular0(light_index); + SyncLightSpecular1(light_index); SyncLightDiffuse(light_index); SyncLightAmbient(light_index); SyncLightPosition(light_index); @@ -734,79 +884,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(); - } -} - void RasterizerOpenGL::SyncCullMode() { const auto& regs = Pica::g_state.regs; @@ -873,13 +950,39 @@ void RasterizerOpenGL::SyncLogicOp() { state.logic_op = PicaToGL::LogicOp(Pica::g_state.regs.output_merger.logic_op); } +void RasterizerOpenGL::SyncColorWriteMask() { + const auto& regs = Pica::g_state.regs; + + auto IsColorWriteEnabled = [&](u32 value) { + return (regs.framebuffer.allow_color_write != 0 && value != 0) ? GL_TRUE : GL_FALSE; + }; + + state.color_mask.red_enabled = IsColorWriteEnabled(regs.output_merger.red_enable); + state.color_mask.green_enabled = IsColorWriteEnabled(regs.output_merger.green_enable); + state.color_mask.blue_enabled = IsColorWriteEnabled(regs.output_merger.blue_enable); + state.color_mask.alpha_enabled = IsColorWriteEnabled(regs.output_merger.alpha_enable); +} + +void RasterizerOpenGL::SyncStencilWriteMask() { + const auto& regs = Pica::g_state.regs; + state.stencil.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0) + ? static_cast<GLuint>(regs.output_merger.stencil_test.write_mask) + : 0; +} + +void RasterizerOpenGL::SyncDepthWriteMask() { + const auto& regs = Pica::g_state.regs; + state.depth.write_mask = (regs.framebuffer.allow_depth_stencil_write != 0 && regs.output_merger.depth_write_enable) + ? GL_TRUE + : GL_FALSE; +} + void RasterizerOpenGL::SyncStencilTest() { const auto& regs = Pica::g_state.regs; state.stencil.test_enabled = regs.output_merger.stencil_test.enable && regs.framebuffer.depth_format == Pica::Regs::DepthFormat::D24S8; state.stencil.test_func = PicaToGL::CompareFunc(regs.output_merger.stencil_test.func); state.stencil.test_ref = regs.output_merger.stencil_test.reference_value; state.stencil.test_mask = regs.output_merger.stencil_test.input_mask; - state.stencil.write_mask = regs.output_merger.stencil_test.write_mask; state.stencil.action_stencil_fail = PicaToGL::StencilOp(regs.output_merger.stencil_test.action_stencil_fail); state.stencil.action_depth_fail = PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_fail); state.stencil.action_depth_pass = PicaToGL::StencilOp(regs.output_merger.stencil_test.action_depth_pass); @@ -891,11 +994,6 @@ void RasterizerOpenGL::SyncDepthTest() { regs.output_merger.depth_write_enable == 1; state.depth.test_func = regs.output_merger.depth_test_enable == 1 ? PicaToGL::CompareFunc(regs.output_merger.depth_test_func) : GL_ALWAYS; - state.color_mask.red_enabled = regs.output_merger.red_enable; - state.color_mask.green_enabled = regs.output_merger.green_enable; - state.color_mask.blue_enabled = regs.output_merger.blue_enable; - state.color_mask.alpha_enabled = regs.output_merger.alpha_enable; - state.depth.write_mask = regs.output_merger.depth_write_enable ? GL_TRUE : GL_FALSE; } void RasterizerOpenGL::SyncCombinerColor() { @@ -982,229 +1080,3 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) { uniform_block_data.dirty = true; } } - -void RasterizerOpenGL::SyncDrawState() { - const auto& regs = Pica::g_state.regs; - - // Sync the viewport - GLsizei viewport_width = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_x).ToFloat32() * 2; - GLsizei viewport_height = (GLsizei)Pica::float24::FromRaw(regs.viewport_size_y).ToFloat32() * 2; - - // OpenGL uses different y coordinates, so negate corner offset and flip origin - // TODO: Ensure viewport_corner.x should not be negated or origin flipped - // TODO: Use floating-point viewports for accuracy if supported - glViewport((GLsizei)regs.viewport_corner.x, - (GLsizei)regs.viewport_corner.y, - viewport_width, viewport_height); - - // Sync bound texture(s), upload if not cached - const auto pica_textures = regs.GetTextures(); - for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) { - const auto& texture = pica_textures[texture_index]; - - if (texture.enabled) { - texture_samplers[texture_index].SyncWithConfig(texture.config); - res_cache.LoadAndBindTexture(state, texture_index, texture); - } else { - state.texture_units[texture_index].texture_2d = 0; - } - } - - state.draw.uniform_buffer = uniform_buffer.handle; - state.Apply(); -} - -MICROPROFILE_DEFINE(OpenGL_FramebufferReload, "OpenGL", "FB Reload", MP_RGB(70, 70, 200)); - -void RasterizerOpenGL::ReloadColorBuffer() { - u8* color_buffer = Memory::GetPhysicalPointer(cached_fb_color_addr); - - if (color_buffer == nullptr) - return; - - MICROPROFILE_SCOPE(OpenGL_FramebufferReload); - - u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format); - - std::unique_ptr<u8[]> temp_fb_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]); - - // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary. - for (int y = 0; y < fb_color_texture.height; ++y) { - for (int x = 0; x < fb_color_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_color_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_color_texture.height - 1 - y) * fb_color_texture.width) * bytes_per_pixel; - - u8* pixel = color_buffer + dst_offset; - memcpy(&temp_fb_color_buffer[gl_pixel_index], pixel, bytes_per_pixel); - } - } - - state.texture_units[0].texture_2d = fb_color_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_color_texture.width, fb_color_texture.height, - fb_color_texture.gl_format, fb_color_texture.gl_type, temp_fb_color_buffer.get()); - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - -void RasterizerOpenGL::ReloadDepthBuffer() { - if (cached_fb_depth_addr == 0) - return; - - // TODO: Appears to work, but double-check endianness of depth values and order of depth-stencil - u8* depth_buffer = Memory::GetPhysicalPointer(cached_fb_depth_addr); - - if (depth_buffer == nullptr) - return; - - MICROPROFILE_SCOPE(OpenGL_FramebufferReload); - - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format); - - // OpenGL needs 4 bpp alignment for D24 - u32 gl_bpp = bytes_per_pixel == 3 ? 4 : bytes_per_pixel; - - std::unique_ptr<u8[]> temp_fb_depth_buffer(new u8[fb_depth_texture.width * fb_depth_texture.height * gl_bpp]); - - u8* temp_fb_depth_data = bytes_per_pixel == 3 ? (temp_fb_depth_buffer.get() + 1) : temp_fb_depth_buffer.get(); - - if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width); - - u8* pixel = depth_buffer + dst_offset; - u32 depth_stencil = *(u32*)pixel; - ((u32*)temp_fb_depth_data)[gl_pixel_index] = (depth_stencil << 8) | (depth_stencil >> 24); - } - } - } else { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width) * gl_bpp; - - u8* pixel = depth_buffer + dst_offset; - memcpy(&temp_fb_depth_data[gl_pixel_index], pixel, bytes_per_pixel); - } - } - } - - state.texture_units[0].texture_2d = fb_depth_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) { - // TODO(Subv): There is a bug with Intel Windows drivers that makes glTexSubImage2D not change the stencil buffer. - // The bug has been reported to Intel (https://communities.intel.com/message/324464) - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, fb_depth_texture.width, fb_depth_texture.height, 0, - GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, temp_fb_depth_buffer.get()); - } else { - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, fb_depth_texture.width, fb_depth_texture.height, - fb_depth_texture.gl_format, fb_depth_texture.gl_type, temp_fb_depth_buffer.get()); - } - - state.texture_units[0].texture_2d = 0; - state.Apply(); -} - -Common::Profiling::TimingCategory buffer_commit_category("Framebuffer Commit"); -MICROPROFILE_DEFINE(OpenGL_FramebufferCommit, "OpenGL", "FB Commit", MP_RGB(70, 70, 200)); - -void RasterizerOpenGL::CommitColorBuffer() { - if (cached_fb_color_addr != 0) { - u8* color_buffer = Memory::GetPhysicalPointer(cached_fb_color_addr); - - if (color_buffer != nullptr) { - Common::Profiling::ScopeTimer timer(buffer_commit_category); - MICROPROFILE_SCOPE(OpenGL_FramebufferCommit); - - u32 bytes_per_pixel = Pica::Regs::BytesPerColorPixel(fb_color_texture.format); - - std::unique_ptr<u8[]> temp_gl_color_buffer(new u8[fb_color_texture.width * fb_color_texture.height * bytes_per_pixel]); - - state.texture_units[0].texture_2d = fb_color_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glGetTexImage(GL_TEXTURE_2D, 0, fb_color_texture.gl_format, fb_color_texture.gl_type, temp_gl_color_buffer.get()); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - // Directly copy pixels. Internal OpenGL color formats are consistent so no conversion is necessary. - for (int y = 0; y < fb_color_texture.height; ++y) { - for (int x = 0; x < fb_color_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_color_texture.width * bytes_per_pixel; - u32 gl_pixel_index = x * bytes_per_pixel + (fb_color_texture.height - 1 - y) * fb_color_texture.width * bytes_per_pixel; - - u8* pixel = color_buffer + dst_offset; - memcpy(pixel, &temp_gl_color_buffer[gl_pixel_index], bytes_per_pixel); - } - } - } - } -} - -void RasterizerOpenGL::CommitDepthBuffer() { - if (cached_fb_depth_addr != 0) { - // TODO: Output seems correct visually, but doesn't quite match sw renderer output. One of them is wrong. - u8* depth_buffer = Memory::GetPhysicalPointer(cached_fb_depth_addr); - - if (depth_buffer != nullptr) { - Common::Profiling::ScopeTimer timer(buffer_commit_category); - MICROPROFILE_SCOPE(OpenGL_FramebufferCommit); - - u32 bytes_per_pixel = Pica::Regs::BytesPerDepthPixel(fb_depth_texture.format); - - // OpenGL needs 4 bpp alignment for D24 - u32 gl_bpp = bytes_per_pixel == 3 ? 4 : bytes_per_pixel; - - std::unique_ptr<u8[]> temp_gl_depth_buffer(new u8[fb_depth_texture.width * fb_depth_texture.height * gl_bpp]); - - state.texture_units[0].texture_2d = fb_depth_texture.texture.handle; - state.Apply(); - - glActiveTexture(GL_TEXTURE0); - glGetTexImage(GL_TEXTURE_2D, 0, fb_depth_texture.gl_format, fb_depth_texture.gl_type, temp_gl_depth_buffer.get()); - - state.texture_units[0].texture_2d = 0; - state.Apply(); - - u8* temp_gl_depth_data = bytes_per_pixel == 3 ? (temp_gl_depth_buffer.get() + 1) : temp_gl_depth_buffer.get(); - - if (fb_depth_texture.format == Pica::Regs::DepthFormat::D24S8) { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width); - - u8* pixel = depth_buffer + dst_offset; - u32 depth_stencil = ((u32*)temp_gl_depth_data)[gl_pixel_index]; - *(u32*)pixel = (depth_stencil >> 8) | (depth_stencil << 24); - } - } - } else { - for (int y = 0; y < fb_depth_texture.height; ++y) { - for (int x = 0; x < fb_depth_texture.width; ++x) { - const u32 coarse_y = y & ~7; - u32 dst_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * fb_depth_texture.width * bytes_per_pixel; - u32 gl_pixel_index = (x + (fb_depth_texture.height - 1 - y) * fb_depth_texture.width) * gl_bpp; - - u8* pixel = depth_buffer + dst_offset; - memcpy(pixel, &temp_gl_depth_data[gl_pixel_index], bytes_per_pixel); - } - } - } - } - } -} diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index fc85aa3ff..82fa61742 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -4,22 +4,33 @@ #pragma once +#include <array> #include <cstddef> #include <cstring> #include <memory> #include <vector> #include <unordered_map> +#include <glad/glad.h> + +#include "common/bit_field.h" #include "common/common_types.h" #include "common/hash.h" +#include "common/vector_math.h" + +#include "core/hw/gpu.h" #include "video_core/pica.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" #include "video_core/renderer_opengl/pica_to_gl.h" -#include "video_core/shader/shader_interpreter.h" +#include "video_core/shader/shader.h" + +struct ScreenInfo; /** * This struct contains all state used to generate the GLSL shader program that emulates the current @@ -38,36 +49,18 @@ struct PicaShaderConfig { res.alpha_test_func = regs.output_merger.alpha_test.enable ? regs.output_merger.alpha_test.func.Value() : Pica::Regs::CompareFunc::Always; - // Copy relevant TevStageConfig fields only. We're doing this manually (instead of calling - // the GetTevStages() function) because BitField explicitly disables copies. - - res.tev_stages[0].sources_raw = regs.tev_stage0.sources_raw; - res.tev_stages[1].sources_raw = regs.tev_stage1.sources_raw; - res.tev_stages[2].sources_raw = regs.tev_stage2.sources_raw; - res.tev_stages[3].sources_raw = regs.tev_stage3.sources_raw; - res.tev_stages[4].sources_raw = regs.tev_stage4.sources_raw; - res.tev_stages[5].sources_raw = regs.tev_stage5.sources_raw; - - res.tev_stages[0].modifiers_raw = regs.tev_stage0.modifiers_raw; - res.tev_stages[1].modifiers_raw = regs.tev_stage1.modifiers_raw; - res.tev_stages[2].modifiers_raw = regs.tev_stage2.modifiers_raw; - res.tev_stages[3].modifiers_raw = regs.tev_stage3.modifiers_raw; - res.tev_stages[4].modifiers_raw = regs.tev_stage4.modifiers_raw; - res.tev_stages[5].modifiers_raw = regs.tev_stage5.modifiers_raw; - - res.tev_stages[0].ops_raw = regs.tev_stage0.ops_raw; - res.tev_stages[1].ops_raw = regs.tev_stage1.ops_raw; - res.tev_stages[2].ops_raw = regs.tev_stage2.ops_raw; - res.tev_stages[3].ops_raw = regs.tev_stage3.ops_raw; - res.tev_stages[4].ops_raw = regs.tev_stage4.ops_raw; - res.tev_stages[5].ops_raw = regs.tev_stage5.ops_raw; - - res.tev_stages[0].scales_raw = regs.tev_stage0.scales_raw; - res.tev_stages[1].scales_raw = regs.tev_stage1.scales_raw; - res.tev_stages[2].scales_raw = regs.tev_stage2.scales_raw; - res.tev_stages[3].scales_raw = regs.tev_stage3.scales_raw; - res.tev_stages[4].scales_raw = regs.tev_stage4.scales_raw; - res.tev_stages[5].scales_raw = regs.tev_stage5.scales_raw; + // Copy relevant tev stages fields. + // We don't sync const_color here because of the high variance, it is a + // shader uniform instead. + const auto& tev_stages = regs.GetTevStages(); + DEBUG_ASSERT(res.tev_stages.size() == tev_stages.size()); + for (size_t i = 0; i < tev_stages.size(); i++) { + const auto& tev_stage = tev_stages[i]; + res.tev_stages[i].sources_raw = tev_stage.sources_raw; + res.tev_stages[i].modifiers_raw = tev_stage.modifiers_raw; + res.tev_stages[i].ops_raw = tev_stage.ops_raw; + res.tev_stages[i].scales_raw = tev_stage.scales_raw; + } res.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() | @@ -191,16 +184,17 @@ public: RasterizerOpenGL(); ~RasterizerOpenGL() override; - void InitObjects() override; - void Reset() override; void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, const Pica::Shader::OutputVertex& v2) override; void DrawTriangles() override; - void FlushFramebuffer() override; void NotifyPicaRegisterChanged(u32 id) override; + void FlushAll() override; void FlushRegion(PAddr addr, u32 size) override; - void InvalidateRegion(PAddr addr, u32 size) override; + void FlushAndInvalidateRegion(PAddr addr, u32 size) override; + bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) override; + bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) override; + bool AccelerateDisplay(const GPU::Regs::FramebufferConfig& config, PAddr framebuffer_addr, u32 pixel_stride, ScreenInfo& screen_info) override; /// OpenGL shader generated for a given Pica register state struct PicaShader { @@ -210,26 +204,6 @@ public: private: - /// Structure used for storing information about color textures - struct TextureInfo { - OGLTexture texture; - GLsizei width; - GLsizei height; - Pica::Regs::ColorFormat format; - GLenum gl_format; - GLenum gl_type; - }; - - /// Structure used for storing information about depth textures - struct DepthTextureInfo { - OGLTexture texture; - GLsizei width; - GLsizei height; - Pica::Regs::DepthFormat format; - GLenum gl_format; - GLenum gl_type; - }; - struct SamplerInfo { using TextureConfig = Pica::Regs::TextureConfig; @@ -311,18 +285,9 @@ private: static_assert(sizeof(UniformData) == 0x310, "The size of the UniformData structure has changed, update the structure in the shader"); static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec"); - /// Reconfigure the OpenGL color texture to use the given format and dimensions - void ReconfigureColorTexture(TextureInfo& texture, Pica::Regs::ColorFormat format, u32 width, u32 height); - - /// Reconfigure the OpenGL depth texture to use the given format and dimensions - void ReconfigureDepthTexture(DepthTextureInfo& texture, Pica::Regs::DepthFormat format, u32 width, u32 height); - /// Sets the OpenGL shader in accordance with the current PICA register state void SetShader(); - /// Syncs the state and contents of the OpenGL framebuffer to match the current PICA framebuffer - void SyncFramebuffer(); - /// Syncs the cull mode to match the PICA register void SyncCullMode(); @@ -344,78 +309,57 @@ private: /// Syncs the logic op states to match the PICA register void SyncLogicOp(); + /// Syncs the color write mask to match the PICA register state + void SyncColorWriteMask(); + + /// Syncs the stencil write mask to match the PICA register state + void SyncStencilWriteMask(); + + /// Syncs the depth write mask to match the PICA register state + void SyncDepthWriteMask(); + /// Syncs the stencil test states to match the PICA register void SyncStencilTest(); /// Syncs the depth test states to match the PICA register void SyncDepthTest(); - /// Syncs the TEV constant color to match the PICA register - void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); - /// Syncs the TEV combiner color buffer to match the PICA register void SyncCombinerColor(); + /// Syncs the TEV constant color to match the PICA register + void SyncTevConstColor(int tev_index, const Pica::Regs::TevStageConfig& tev_stage); + /// Syncs the lighting global ambient color to match the PICA register void SyncGlobalAmbient(); /// Syncs the lighting lookup tables void SyncLightingLUT(unsigned index); - /// Syncs the specified light's diffuse color to match the PICA register - void SyncLightDiffuse(int light_index); - - /// Syncs the specified light's ambient color to match the PICA register - void SyncLightAmbient(int light_index); - - /// Syncs the specified light's position to match the PICA register - void SyncLightPosition(int light_index); - /// Syncs the specified light's specular 0 color to match the PICA register void SyncLightSpecular0(int light_index); /// Syncs the specified light's specular 1 color to match the PICA register void SyncLightSpecular1(int light_index); - /// Syncs the remaining OpenGL drawing state to match the current PICA state - void SyncDrawState(); - - /// Copies the 3DS color framebuffer into the OpenGL color framebuffer texture - void ReloadColorBuffer(); + /// Syncs the specified light's diffuse color to match the PICA register + void SyncLightDiffuse(int light_index); - /// Copies the 3DS depth framebuffer into the OpenGL depth framebuffer texture - void ReloadDepthBuffer(); + /// Syncs the specified light's ambient color to match the PICA register + void SyncLightAmbient(int light_index); - /** - * 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(); + /// Syncs the specified light's position to match the PICA register + void SyncLightPosition(int light_index); - /** - * Save the current OpenGL depth framebuffer to the current PICA framebuffer in 3DS memory - * Loads the OpenGL framebuffer textures into temporary buffers - * Then copies into the 3DS framebuffer using proper Morton order - */ - void CommitDepthBuffer(); + OpenGLState state; RasterizerCacheOpenGL res_cache; std::vector<HardwareVertex> vertex_batch; - OpenGLState state; - - PAddr cached_fb_color_addr; - PAddr cached_fb_depth_addr; - - // Hardware rasterizer - std::array<SamplerInfo, 3> texture_samplers; - TextureInfo fb_color_texture; - DepthTextureInfo fb_depth_texture; - std::unordered_map<PicaShaderConfig, std::unique_ptr<PicaShader>> shader_cache; const PicaShader* current_shader = nullptr; + bool shader_dirty; struct { UniformData data; @@ -423,11 +367,12 @@ private: bool dirty; } uniform_block_data; + std::array<SamplerInfo, 3> texture_samplers; OGLVertexArray vertex_array; OGLBuffer vertex_buffer; OGLBuffer uniform_buffer; OGLFramebuffer framebuffer; - std::array<OGLTexture, 6> lighting_lut; + std::array<OGLTexture, 6> lighting_luts; std::array<std::array<GLvec4, 256>, 6> lighting_lut_data; }; diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index a9ad46fe0..7efd0038a 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -2,8 +2,19 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "common/hash.h" -#include "common/make_unique.h" +#include <algorithm> +#include <atomic> +#include <cstring> +#include <iterator> +#include <unordered_set> +#include <utility> +#include <vector> + +#include <glad/glad.h> + +#include "common/bit_field.h" +#include "common/emu_window.h" +#include "common/logging/log.h" #include "common/math_util.h" #include "common/microprofile.h" #include "common/vector_math.h" @@ -11,71 +22,693 @@ #include "core/memory.h" #include "video_core/debug_utils/debug_utils.h" +#include "video_core/pica_state.h" #include "video_core/renderer_opengl/gl_rasterizer_cache.h" -#include "video_core/renderer_opengl/pica_to_gl.h" +#include "video_core/renderer_opengl/gl_state.h" +#include "video_core/utils.h" +#include "video_core/video_core.h" + +struct FormatTuple { + GLint internal_format; + GLenum format; + GLenum type; +}; + +static const std::array<FormatTuple, 5> fb_format_tuples = {{ + { GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8 }, // RGBA8 + { GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE }, // RGB8 + { GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1 }, // RGB5A1 + { GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5 }, // RGB565 + { GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4 }, // RGBA4 +}}; + +static const std::array<FormatTuple, 4> depth_format_tuples = {{ + { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT }, // D16 + {}, + { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT }, // D24 + { GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8 }, // D24S8 +}}; + +RasterizerCacheOpenGL::RasterizerCacheOpenGL() { + transfer_framebuffers[0].Create(); + transfer_framebuffers[1].Create(); +} RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { - InvalidateAll(); + FlushAll(); +} + +static void MortonCopyPixels(CachedSurface::PixelFormat pixel_format, u32 width, u32 height, u32 bytes_per_pixel, u32 gl_bytes_per_pixel, u8* morton_data, u8* gl_data, bool morton_to_gl) { + using PixelFormat = CachedSurface::PixelFormat; + + u8* data_ptrs[2]; + u32 depth_stencil_shifts[2] = {24, 8}; + + if (morton_to_gl) { + std::swap(depth_stencil_shifts[0], depth_stencil_shifts[1]); + } + + if (pixel_format == PixelFormat::D24S8) { + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~7; + u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + // 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 { + for (unsigned y = 0; y < height; ++y) { + for (unsigned x = 0; x < width; ++x) { + const u32 coarse_y = y & ~7; + u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * width * bytes_per_pixel; + u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel; + + data_ptrs[morton_to_gl] = morton_data + morton_offset; + data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index]; + + memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel); + } + } + } +} + +bool RasterizerCacheOpenGL::BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& dst_rect) { + using SurfaceType = CachedSurface::SurfaceType; + + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Make sure textures aren't bound to texture units, since going to bind them to framebuffer components + OpenGLState::ResetTexture(src_tex); + OpenGLState::ResetTexture(dst_tex); + + // Keep track of previous framebuffer bindings + GLuint old_fbs[2] = { cur_state.draw.read_framebuffer, cur_state.draw.draw_framebuffer }; + cur_state.draw.read_framebuffer = transfer_framebuffers[0].handle; + cur_state.draw.draw_framebuffer = transfer_framebuffers[1].handle; + cur_state.Apply(); + + u32 buffers = 0; + + if (type == SurfaceType::Color || type == SurfaceType::Texture) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_COLOR_BUFFER_BIT; + } else if (type == SurfaceType::Depth) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + buffers = GL_DEPTH_BUFFER_BIT; + } else if (type == SurfaceType::DepthStencil) { + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); + + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); + + buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + } + + if (OpenGLState::CheckFBStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } + + if (OpenGLState::CheckFBStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + return false; + } + + glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, + dst_rect.left, dst_rect.top, dst_rect.right, dst_rect.bottom, + buffers, buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); + + // Restore previous framebuffer bindings + cur_state.draw.read_framebuffer = old_fbs[0]; + cur_state.draw.draw_framebuffer = old_fbs[1]; + cur_state.Apply(); + + return true; +} + +bool RasterizerCacheOpenGL::TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect) { + using SurfaceType = CachedSurface::SurfaceType; + + if (!CachedSurface::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) { + return false; + } + + return BlitTextures(src_surface->texture.handle, dst_surface->texture.handle, CachedSurface::GetFormatType(src_surface->pixel_format), src_rect, dst_rect); +} + +static void AllocateSurfaceTexture(GLuint texture, CachedSurface::PixelFormat pixel_format, u32 width, u32 height) { + // Allocate an uninitialized texture of appropriate size and format for the surface + using SurfaceType = CachedSurface::SurfaceType; + + OpenGLState cur_state = OpenGLState::GetCurState(); + + // Keep track of previous texture bindings + GLuint old_tex = cur_state.texture_units[0].texture_2d; + cur_state.texture_units[0].texture_2d = texture; + cur_state.Apply(); + glActiveTexture(GL_TEXTURE0); + + SurfaceType type = CachedSurface::GetFormatType(pixel_format); + + FormatTuple tuple; + if (type == SurfaceType::Color) { + ASSERT((size_t)pixel_format < fb_format_tuples.size()); + tuple = fb_format_tuples[(unsigned int)pixel_format]; + } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) { + size_t tuple_idx = (size_t)pixel_format - 14; + ASSERT(tuple_idx < depth_format_tuples.size()); + tuple = depth_format_tuples[tuple_idx]; + } else { + tuple = { GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE }; + } + + glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, width, height, 0, + tuple.format, tuple.type, nullptr); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Restore previous texture bindings + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); } -MICROPROFILE_DEFINE(OpenGL_TextureUpload, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); +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; -void RasterizerCacheOpenGL::LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info) { - const auto cached_texture = texture_cache.find(info.physical_address); + new_surface->is_tiled = params.is_tiled; + new_surface->pixel_format = params.pixel_format; + new_surface->dirty = false; - if (cached_texture != texture_cache.end()) { - state.texture_units[texture_unit].texture_2d = cached_texture->second->texture.handle; - state.Apply(); + 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 { - MICROPROFILE_SCOPE(OpenGL_TextureUpload); + // 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); - std::unique_ptr<CachedTexture> new_texture = Common::make_unique<CachedTexture>(); + cur_state.texture_units[0].texture_2d = old_tex; + cur_state.Apply(); + } + + Memory::RasterizerMarkRegionCached(new_surface->addr, new_surface->size, 1); + surface_cache.add(std::make_pair(boost::icl::interval<PAddr>::right_open(new_surface->addr, new_surface->addr + new_surface->size), std::set<std::shared_ptr<CachedSurface>>({ new_surface }))); + return new_surface.get(); +} - new_texture->texture.Create(); - state.texture_units[texture_unit].texture_2d = new_texture->texture.handle; - state.Apply(); - glActiveTexture(GL_TEXTURE0 + texture_unit); +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; + } - u8* texture_src_data = Memory::GetPhysicalPointer(info.physical_address); + u32 total_pixels = params.width * params.height; + u32 params_size = total_pixels * CachedSurface::GetFormatBpp(params.pixel_format) / 8; - 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); + // Attempt to find encompassing surfaces + CachedSurface* best_subrect_surface = nullptr; + float subrect_surface_goodness = -1.f; - std::unique_ptr<Math::Vec4<u8>[]> temp_texture_buffer_rgba(new Math::Vec4<u8>[info.width * info.height]); + auto surface_interval = boost::icl::interval<PAddr>::right_open(params.addr, params.addr + params_size); + auto cache_upper_bound = surface_cache.upper_bound(surface_interval); + for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) { + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) { + CachedSurface* surface = it2->get(); - for (int y = 0; y < info.height; ++y) { - for (int x = 0; x < info.width; ++x) { - temp_texture_buffer_rgba[x + info.width * y] = Pica::DebugUtils::LookupTexture(texture_src_data, x, info.height - 1 - y, info); + // Check if the request is contained in the surface + if (params.addr >= surface->addr && + params.addr + params_size - 1 <= surface->addr + surface->size - 1 && + params.pixel_format == surface->pixel_format) + { + // Make sure optional param-matching criteria are fulfilled + bool tiling_match = (params.is_tiled == surface->is_tiled); + bool res_scale_match = (params.res_scale_width == surface->res_scale_width && params.res_scale_height == surface->res_scale_height); + if (!match_res_scale || res_scale_match) { + // Prioritize same-tiling and highest resolution surfaces + float match_goodness = (float)tiling_match + surface->res_scale_width * surface->res_scale_height; + if (match_goodness > subrect_surface_goodness || surface->dirty) { + subrect_surface_goodness = match_goodness; + best_subrect_surface = surface; + } + } } } + } + + // Return the best subrect surface if found + if (best_subrect_surface != nullptr) { + unsigned int bytes_per_pixel = (CachedSurface::GetFormatBpp(best_subrect_surface->pixel_format) / 8); + + int x0, y0; + + if (!params.is_tiled) { + u32 begin_pixel_index = (params.addr - best_subrect_surface->addr) / bytes_per_pixel; + x0 = begin_pixel_index % best_subrect_surface->width; + y0 = begin_pixel_index / best_subrect_surface->width; + + out_rect = MathUtil::Rectangle<int>(x0, y0, x0 + params.width, y0 + params.height); + } else { + u32 bytes_per_tile = 8 * 8 * bytes_per_pixel; + u32 tiles_per_row = best_subrect_surface->width / 8; + + u32 begin_tile_index = (params.addr - best_subrect_surface->addr) / bytes_per_tile; + x0 = begin_tile_index % tiles_per_row * 8; + y0 = begin_tile_index / tiles_per_row * 8; + + // Tiled surfaces are flipped vertically in the rasterizer vs. 3DS memory. + out_rect = MathUtil::Rectangle<int>(x0, best_subrect_surface->height - y0, x0 + params.width, best_subrect_surface->height - (y0 + params.height)); + } + + 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); + } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, info.width, info.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_texture_buffer_rgba.get()); + 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); +} - texture_cache.emplace(info.physical_address, std::move(new_texture)); +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; } -void RasterizerCacheOpenGL::InvalidateInRange(PAddr addr, u32 size, bool ignore_hash) { - // TODO: Optimize by also inserting upper bound (addr + size) of each texture into the same map and also narrow using lower_bound - auto cache_upper_bound = texture_cache.upper_bound(addr + size); +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); - for (auto it = texture_cache.begin(); it != cache_upper_bound;) { - const auto& info = *it->second; + 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]; - // Flush the texture only if the memory region intersects and a change is detected - if (MathUtil::IntervalsIntersect(addr, size, info.addr, info.size) && - (ignore_hash || info.hash != Common::ComputeHash64(Memory::GetPhysicalPointer(info.addr), info.size))) { + 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); - it = texture_cache.erase(it); + 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 { - ++it; + // 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() { - texture_cache.clear(); +void RasterizerCacheOpenGL::FlushAll() { + for (auto& surfaces : surface_cache) { + for (auto& surface : surfaces.second) { + FlushSurface(surface.get()); + } + } } diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h index b69651427..225596415 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h @@ -4,40 +4,219 @@ #pragma once -#include <map> +#include <array> #include <memory> +#include <set> +#include <tuple> + +#include <boost/icl/interval_map.hpp> +#include <glad/glad.h> + +#include "common/assert.h" +#include "common/common_funcs.h" +#include "common/common_types.h" + +#include "core/hw/gpu.h" #include "video_core/pica.h" -#include "video_core/debug_utils/debug_utils.h" #include "video_core/renderer_opengl/gl_resource_manager.h" -#include "video_core/renderer_opengl/gl_state.h" + +namespace MathUtil { +template <class T> struct Rectangle; +} + +struct CachedSurface; + +using SurfaceCache = boost::icl::interval_map<PAddr, std::set<std::shared_ptr<CachedSurface>>>; + +struct CachedSurface { + enum class PixelFormat { + // First 5 formats are shared between textures and color buffers + RGBA8 = 0, + RGB8 = 1, + RGB5A1 = 2, + RGB565 = 3, + RGBA4 = 4, + + // Texture-only formats + IA8 = 5, + RG8 = 6, + I8 = 7, + A8 = 8, + IA4 = 9, + I4 = 10, + A4 = 11, + ETC1 = 12, + ETC1A4 = 13, + + // Depth buffer-only formats + D16 = 14, + // gap + D24 = 16, + D24S8 = 17, + + Invalid = 255, + }; + + enum class SurfaceType { + Color = 0, + Texture = 1, + Depth = 2, + DepthStencil = 3, + Invalid = 4, + }; + + static unsigned int GetFormatBpp(CachedSurface::PixelFormat format) { + static const std::array<unsigned int, 18> bpp_table = { + 32, // RGBA8 + 24, // RGB8 + 16, // RGB5A1 + 16, // RGB565 + 16, // RGBA4 + 16, // IA8 + 16, // RG8 + 8, // I8 + 8, // A8 + 8, // IA4 + 4, // I4 + 4, // A4 + 4, // ETC1 + 8, // ETC1A4 + 16, // D16 + 0, + 24, // D24 + 32, // D24S8 + }; + + ASSERT((unsigned int)format < ARRAY_SIZE(bpp_table)); + return bpp_table[(unsigned int)format]; + } + + static PixelFormat PixelFormatFromTextureFormat(Pica::Regs::TextureFormat format) { + return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid; + } + + static PixelFormat PixelFormatFromColorFormat(Pica::Regs::ColorFormat format) { + return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; + } + + static PixelFormat PixelFormatFromDepthFormat(Pica::Regs::DepthFormat format) { + return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14) : PixelFormat::Invalid; + } + + static PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) { + switch (format) { + // RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat + case GPU::Regs::PixelFormat::RGB565: + return PixelFormat::RGB565; + case GPU::Regs::PixelFormat::RGB5A1: + return PixelFormat::RGB5A1; + default: + return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid; + } + } + + static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { + SurfaceType a_type = GetFormatType(pixel_format_a); + SurfaceType b_type = GetFormatType(pixel_format_b); + + if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) && (b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) { + return true; + } + + if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) { + return true; + } + + if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) { + return true; + } + + return false; + } + + static SurfaceType GetFormatType(PixelFormat pixel_format) { + if ((unsigned int)pixel_format < 5) { + return SurfaceType::Color; + } + + if ((unsigned int)pixel_format < 14) { + return SurfaceType::Texture; + } + + if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) { + return SurfaceType::Depth; + } + + if (pixel_format == PixelFormat::D24S8) { + return SurfaceType::DepthStencil; + } + + return SurfaceType::Invalid; + } + + u32 GetScaledWidth() const { + return (u32)(width * res_scale_width); + } + + u32 GetScaledHeight() const { + return (u32)(height * res_scale_height); + } + + PAddr addr; + u32 size; + + PAddr min_valid; + PAddr max_valid; + + OGLTexture texture; + u32 width; + u32 height; + u32 stride = 0; + float res_scale_width = 1.f; + float res_scale_height = 1.f; + + bool is_tiled; + PixelFormat pixel_format; + bool dirty; +}; class RasterizerCacheOpenGL : NonCopyable { public: + RasterizerCacheOpenGL(); ~RasterizerCacheOpenGL(); + /// Blits one texture to another + bool BlitTextures(GLuint src_tex, GLuint dst_tex, CachedSurface::SurfaceType type, const MathUtil::Rectangle<int>& src_rect, const MathUtil::Rectangle<int>& dst_rect); + + /// Attempt to blit one surface's texture to another + bool TryBlitSurfaces(CachedSurface* src_surface, const MathUtil::Rectangle<int>& src_rect, CachedSurface* dst_surface, const MathUtil::Rectangle<int>& dst_rect); + /// Loads a texture from 3DS memory to OpenGL and caches it (if not already cached) - void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::DebugUtils::TextureInfo& info); + CachedSurface* GetSurface(const CachedSurface& params, bool match_res_scale, bool load_if_create); - void LoadAndBindTexture(OpenGLState &state, unsigned texture_unit, const Pica::Regs::FullTextureConfig& config) { - LoadAndBindTexture(state, texture_unit, Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format)); - } + /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from 3DS memory to OpenGL and caches it (if not already cached) + CachedSurface* GetSurfaceRect(const CachedSurface& params, bool match_res_scale, bool load_if_create, MathUtil::Rectangle<int>& out_rect); - /// Invalidate any cached resource intersecting the specified region. - void InvalidateInRange(PAddr addr, u32 size, bool ignore_hash = false); + /// Gets a surface based on the texture configuration + CachedSurface* GetTextureSurface(const Pica::Regs::FullTextureConfig& config); - /// Invalidate all cached OpenGL resources tracked by this cache manager - void InvalidateAll(); + /// Gets the color and depth surfaces and rect (resolution scaled) based on the framebuffer configuration + std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>> GetFramebufferSurfaces(const Pica::Regs::FramebufferConfig& config); -private: - struct CachedTexture { - OGLTexture texture; - GLuint width; - GLuint height; - u32 size; - u64 hash; - PAddr addr; - }; + /// Attempt to get a surface that exactly matches the fill region and format + CachedSurface* TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config); + + /// Write the surface back to memory + void FlushSurface(CachedSurface* surface); - std::map<PAddr, std::unique_ptr<CachedTexture>> texture_cache; + /// Write any cached resources overlapping the region back to memory (if dirty) and optionally invalidate them in the cache + void FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface, bool invalidate); + + /// Flush all cached resources tracked by this cache manager + void FlushAll(); + +private: + SurfaceCache surface_cache; + OGLFramebuffer transfer_framebuffers[2]; }; diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index ee4b54ab9..9011caa39 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -2,9 +2,17 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> +#include <cstddef> + +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/logging/log.h" + #include "video_core/pica.h" #include "video_core/renderer_opengl/gl_rasterizer.h" #include "video_core/renderer_opengl/gl_shader_gen.h" +#include "video_core/renderer_opengl/gl_shader_util.h" using Pica::Regs; using TevStageConfig = Regs::TevStageConfig; @@ -198,6 +206,9 @@ static void AppendColorCombiner(std::string& out, TevStageConfig::Operation oper case Operation::AddThenMultiply: out += "min(" + variable_name + "[0] + " + variable_name + "[1], vec3(1.0)) * " + variable_name + "[2]"; break; + case Operation::Dot3_RGB: + out += "vec3(dot(" + variable_name + "[0] - vec3(0.5), " + variable_name + "[1] - vec3(0.5)) * 4.0)"; + break; default: out += "vec3(0.0)"; LOG_CRITICAL(Render_OpenGL, "Unknown color combiner operation: %u", operation); diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h index 0ca9d2879..3eb07d57a 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.h +++ b/src/video_core/renderer_opengl/gl_shader_gen.h @@ -6,7 +6,7 @@ #include <string> -#include "video_core/renderer_opengl/gl_rasterizer.h" +struct PicaShaderConfig; namespace GLShader { diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index e3f7a5868..dded3db46 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -2,9 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> #include <vector> +#include <glad/glad.h> + #include "common/logging/log.h" #include "video_core/renderer_opengl/gl_shader_util.h" diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 08e4d0b54..02cd9f417 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -2,7 +2,11 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include "video_core/pica.h" +#include <glad/glad.h> + +#include "common/common_funcs.h" +#include "common/logging/log.h" + #include "video_core/renderer_opengl/gl_state.h" OpenGLState OpenGLState::cur_state; @@ -48,17 +52,19 @@ OpenGLState::OpenGLState() { texture_unit.sampler = 0; } - for (auto& lut : lighting_lut) { + for (auto& lut : lighting_luts) { lut.texture_1d = 0; } - draw.framebuffer = 0; + draw.read_framebuffer = 0; + draw.draw_framebuffer = 0; draw.vertex_array = 0; draw.vertex_buffer = 0; + draw.uniform_buffer = 0; draw.shader_program = 0; } -void OpenGLState::Apply() { +void OpenGLState::Apply() const { // Culling if (cull.enabled != cur_state.cull.enabled) { if (cull.enabled) { @@ -175,16 +181,19 @@ void OpenGLState::Apply() { } // Lighting LUTs - for (unsigned i = 0; i < ARRAY_SIZE(lighting_lut); ++i) { - if (lighting_lut[i].texture_1d != cur_state.lighting_lut[i].texture_1d) { + for (unsigned i = 0; i < ARRAY_SIZE(lighting_luts); ++i) { + if (lighting_luts[i].texture_1d != cur_state.lighting_luts[i].texture_1d) { glActiveTexture(GL_TEXTURE3 + i); - glBindTexture(GL_TEXTURE_1D, lighting_lut[i].texture_1d); + glBindTexture(GL_TEXTURE_1D, lighting_luts[i].texture_1d); } } // Framebuffer - if (draw.framebuffer != cur_state.draw.framebuffer) { - glBindFramebuffer(GL_FRAMEBUFFER, draw.framebuffer); + if (draw.read_framebuffer != cur_state.draw.read_framebuffer) { + glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer); + } + if (draw.draw_framebuffer != cur_state.draw.draw_framebuffer) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, draw.draw_framebuffer); } // Vertex array @@ -210,45 +219,58 @@ void OpenGLState::Apply() { cur_state = *this; } -void OpenGLState::ResetTexture(GLuint id) { +GLenum OpenGLState::CheckFBStatus(GLenum target) { + GLenum fb_status = glCheckFramebufferStatus(target); + if (fb_status != GL_FRAMEBUFFER_COMPLETE) { + const char* fb_description = (target == GL_READ_FRAMEBUFFER ? "READ" : (target == GL_DRAW_FRAMEBUFFER ? "DRAW" : "UNK")); + LOG_CRITICAL(Render_OpenGL, "OpenGL %s framebuffer check failed, status %X", fb_description, fb_status); + } + + return fb_status; +} + +void OpenGLState::ResetTexture(GLuint handle) { for (auto& unit : cur_state.texture_units) { - if (unit.texture_2d == id) { + if (unit.texture_2d == handle) { unit.texture_2d = 0; } } } -void OpenGLState::ResetSampler(GLuint id) { +void OpenGLState::ResetSampler(GLuint handle) { for (auto& unit : cur_state.texture_units) { - if (unit.sampler == id) { + if (unit.sampler == handle) { unit.sampler = 0; } } } -void OpenGLState::ResetProgram(GLuint id) { - if (cur_state.draw.shader_program == id) { +void OpenGLState::ResetProgram(GLuint handle) { + if (cur_state.draw.shader_program == handle) { cur_state.draw.shader_program = 0; } } -void OpenGLState::ResetBuffer(GLuint id) { - if (cur_state.draw.vertex_buffer == id) { +void OpenGLState::ResetBuffer(GLuint handle) { + if (cur_state.draw.vertex_buffer == handle) { cur_state.draw.vertex_buffer = 0; } - if (cur_state.draw.uniform_buffer == id) { + if (cur_state.draw.uniform_buffer == handle) { cur_state.draw.uniform_buffer = 0; } } -void OpenGLState::ResetVertexArray(GLuint id) { - if (cur_state.draw.vertex_array == id) { +void OpenGLState::ResetVertexArray(GLuint handle) { + if (cur_state.draw.vertex_array == handle) { cur_state.draw.vertex_array = 0; } } -void OpenGLState::ResetFramebuffer(GLuint id) { - if (cur_state.draw.framebuffer == id) { - cur_state.draw.framebuffer = 0; +void OpenGLState::ResetFramebuffer(GLuint handle) { + if (cur_state.draw.read_framebuffer == handle) { + cur_state.draw.read_framebuffer = 0; + } + if (cur_state.draw.draw_framebuffer == handle) { + cur_state.draw.draw_framebuffer = 0; } } diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index e848058d7..24f20e47c 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -63,15 +63,15 @@ public: struct { GLuint texture_1d; // GL_TEXTURE_BINDING_1D - } lighting_lut[6]; + } lighting_luts[6]; struct { - GLuint framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING + GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING + GLuint draw_framebuffer; // GL_DRAW_FRAMEBUFFER_BINDING GLuint vertex_array; // GL_VERTEX_ARRAY_BINDING GLuint vertex_buffer; // GL_ARRAY_BUFFER_BINDING GLuint uniform_buffer; // GL_UNIFORM_BUFFER_BINDING GLuint shader_program; // GL_CURRENT_PROGRAM - bool shader_dirty; } draw; OpenGLState(); @@ -82,14 +82,18 @@ public: } /// Apply this state as the current OpenGL state - void Apply(); - - static void ResetTexture(GLuint id); - static void ResetSampler(GLuint id); - static void ResetProgram(GLuint id); - static void ResetBuffer(GLuint id); - static void ResetVertexArray(GLuint id); - static void ResetFramebuffer(GLuint id); + void Apply() const; + + /// Check the status of the current OpenGL read or draw framebuffer configuration + static GLenum CheckFBStatus(GLenum target); + + /// Resets and unbinds any references to the given resource in the current OpenGL state + static void ResetTexture(GLuint handle); + static void ResetSampler(GLuint handle); + static void ResetProgram(GLuint handle); + static void ResetBuffer(GLuint handle); + static void ResetVertexArray(GLuint handle); + static void ResetFramebuffer(GLuint handle); private: static OpenGLState cur_state; diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 3d6c4e9e5..976d1f364 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -4,9 +4,16 @@ #pragma once +#include <array> +#include <cstddef> + #include <glad/glad.h> +#include "common/assert.h" +#include "common/bit_field.h" +#include "common/common_funcs.h" #include "common/common_types.h" +#include "common/logging/log.h" #include "video_core/pica.h" @@ -22,7 +29,7 @@ inline GLenum TextureFilterMode(Pica::Regs::TextureConfig::TextureFilter mode) { }; // Range check table for input - if (mode >= ARRAY_SIZE(filter_mode_table)) { + if (static_cast<size_t>(mode) >= ARRAY_SIZE(filter_mode_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode %d", mode); UNREACHABLE(); @@ -51,7 +58,7 @@ inline GLenum WrapMode(Pica::Regs::TextureConfig::WrapMode mode) { }; // Range check table for input - if (mode >= ARRAY_SIZE(wrap_mode_table)) { + if (static_cast<size_t>(mode) >= ARRAY_SIZE(wrap_mode_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown texture wrap mode %d", mode); UNREACHABLE(); @@ -91,7 +98,7 @@ inline GLenum BlendFunc(Pica::Regs::BlendFactor factor) { }; // Range check table for input - if ((unsigned)factor >= ARRAY_SIZE(blend_func_table)) { + if (static_cast<size_t>(factor) >= ARRAY_SIZE(blend_func_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown blend factor %d", factor); UNREACHABLE(); @@ -122,7 +129,7 @@ inline GLenum LogicOp(Pica::Regs::LogicOp op) { }; // Range check table for input - if ((unsigned)op >= ARRAY_SIZE(logic_op_table)) { + if (static_cast<size_t>(op) >= ARRAY_SIZE(logic_op_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown logic op %d", op); UNREACHABLE(); @@ -145,7 +152,7 @@ inline GLenum CompareFunc(Pica::Regs::CompareFunc func) { }; // Range check table for input - if ((unsigned)func >= ARRAY_SIZE(compare_func_table)) { + if (static_cast<size_t>(func) >= ARRAY_SIZE(compare_func_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown compare function %d", func); UNREACHABLE(); @@ -168,7 +175,7 @@ inline GLenum StencilOp(Pica::Regs::StencilAction action) { }; // Range check table for input - if ((unsigned)action >= ARRAY_SIZE(stencil_op_table)) { + if (static_cast<size_t>(action) >= ARRAY_SIZE(stencil_op_table)) { LOG_CRITICAL(Render_OpenGL, "Unknown stencil op %d", action); UNREACHABLE(); diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index 11c4d0daf..0e9a0be8b 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -5,23 +5,28 @@ #include <algorithm> #include <cstddef> #include <cstdlib> +#include <memory> + +#include <glad/glad.h> #include "common/assert.h" +#include "common/bit_field.h" #include "common/emu_window.h" #include "common/logging/log.h" #include "common/profiler_reporting.h" +#include "common/synchronized_wrapper.h" -#include "core/memory.h" -#include "core/settings.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" +#include "core/memory.h" +#include "core/settings.h" +#include "core/tracer/recorder.h" -#include "video_core/video_core.h" #include "video_core/debug_utils/debug_utils.h" -#include "video_core/renderer_opengl/gl_rasterizer.h" -#include "video_core/renderer_opengl/gl_shader_util.h" +#include "video_core/rasterizer_interface.h" #include "video_core/renderer_opengl/renderer_opengl.h" +#include "video_core/video_core.h" static const char vertex_shader[] = R"( #version 150 core @@ -107,7 +112,7 @@ void RendererOpenGL::SwapBuffers() { OpenGLState prev_state = OpenGLState::GetCurState(); state.Apply(); - for(int i : {0, 1}) { + for (int i : {0, 1}) { const auto& framebuffer = GPU::g_regs.framebuffer_config[i]; // Main LCD (0): 0x1ED02204, Sub LCD (1): 0x1ED02A04 @@ -117,25 +122,25 @@ void RendererOpenGL::SwapBuffers() { LCD::Read(color_fill.raw, lcd_color_addr); if (color_fill.is_enabled) { - LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, textures[i]); + LoadColorToActiveGLTexture(color_fill.color_r, color_fill.color_g, color_fill.color_b, screen_infos[i].texture); // Resize the texture in case the framebuffer size has changed - textures[i].width = 1; - textures[i].height = 1; + screen_infos[i].texture.width = 1; + screen_infos[i].texture.height = 1; } else { - if (textures[i].width != (GLsizei)framebuffer.width || - textures[i].height != (GLsizei)framebuffer.height || - textures[i].format != framebuffer.color_format) { + if (screen_infos[i].texture.width != (GLsizei)framebuffer.width || + screen_infos[i].texture.height != (GLsizei)framebuffer.height || + screen_infos[i].texture.format != framebuffer.color_format) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. - ConfigureFramebufferTexture(textures[i], framebuffer); + ConfigureFramebufferTexture(screen_infos[i].texture, framebuffer); } - LoadFBToActiveGLTexture(framebuffer, textures[i]); + LoadFBToScreenInfo(framebuffer, screen_infos[i]); // Resize the texture in case the framebuffer size has changed - textures[i].width = framebuffer.width; - textures[i].height = framebuffer.height; + screen_infos[i].texture.width = framebuffer.width; + screen_infos[i].texture.height = framebuffer.height; } } @@ -166,8 +171,8 @@ void RendererOpenGL::SwapBuffers() { /** * Loads framebuffer from emulated memory into the active OpenGL texture. */ -void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, - const TextureInfo& texture) { +void RendererOpenGL::LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, + ScreenInfo& screen_info) { const PAddr framebuffer_addr = framebuffer.active_fb == 0 ? framebuffer.address_left1 : framebuffer.address_left2; @@ -177,8 +182,6 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer_addr, (int)framebuffer.width, (int)framebuffer.height, (int)framebuffer.format); - const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); - int bpp = GPU::Regs::BytesPerPixel(framebuffer.color_format); size_t pixel_stride = framebuffer.stride / bpp; @@ -189,24 +192,34 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& // only allows rows to have a memory alignement of 4. ASSERT(pixel_stride % 4 == 0); - state.texture_units[0].texture_2d = texture.handle; - state.Apply(); + if (!Rasterizer()->AccelerateDisplay(framebuffer, framebuffer_addr, pixel_stride, screen_info)) { + // Reset the screen info's display texture to its own permanent texture + screen_info.display_texture = screen_info.texture.resource.handle; + screen_info.display_texcoords = MathUtil::Rectangle<float>(0.f, 0.f, 1.f, 1.f); - glActiveTexture(GL_TEXTURE0); - glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); + Memory::RasterizerFlushRegion(framebuffer_addr, framebuffer.stride * framebuffer.height); - // Update existing texture - // TODO: Test what happens on hardware when you change the framebuffer dimensions so that they - // differ from the LCD resolution. - // TODO: Applications could theoretically crash Citra here by specifying too large - // framebuffer sizes. We should make sure that this cannot happen. - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, - texture.gl_format, texture.gl_type, framebuffer_data); + const u8* framebuffer_data = Memory::GetPhysicalPointer(framebuffer_addr); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; + state.Apply(); - state.texture_units[0].texture_2d = 0; - state.Apply(); + glActiveTexture(GL_TEXTURE0); + glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)pixel_stride); + + // Update existing texture + // TODO: Test what happens on hardware when you change the framebuffer dimensions so that they + // differ from the LCD resolution. + // TODO: Applications could theoretically crash Citra here by specifying too large + // framebuffer sizes. We should make sure that this cannot happen. + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, framebuffer.width, framebuffer.height, + screen_info.texture.gl_format, screen_info.texture.gl_type, framebuffer_data); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + state.texture_units[0].texture_2d = 0; + state.Apply(); + } } /** @@ -216,7 +229,7 @@ void RendererOpenGL::LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& */ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture) { - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -224,6 +237,9 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color // Update existing texture glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, framebuffer_data); + + state.texture_units[0].texture_2d = 0; + state.Apply(); } /** @@ -233,20 +249,22 @@ void RendererOpenGL::InitOpenGLObjects() { glClearColor(Settings::values.bg_red, Settings::values.bg_green, Settings::values.bg_blue, 0.0f); // Link shaders and get variable locations - program_id = GLShader::LoadProgram(vertex_shader, fragment_shader); - uniform_modelview_matrix = glGetUniformLocation(program_id, "modelview_matrix"); - uniform_color_texture = glGetUniformLocation(program_id, "color_texture"); - attrib_position = glGetAttribLocation(program_id, "vert_position"); - attrib_tex_coord = glGetAttribLocation(program_id, "vert_tex_coord"); + shader.Create(vertex_shader, fragment_shader); + state.draw.shader_program = shader.handle; + state.Apply(); + uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix"); + uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture"); + attrib_position = glGetAttribLocation(shader.handle, "vert_position"); + attrib_tex_coord = glGetAttribLocation(shader.handle, "vert_tex_coord"); // Generate VBO handle for drawing - glGenBuffers(1, &vertex_buffer_handle); + vertex_buffer.Create(); // Generate VAO - glGenVertexArrays(1, &vertex_array_handle); + vertex_array.Create(); - state.draw.vertex_array = vertex_array_handle; - state.draw.vertex_buffer = vertex_buffer_handle; + state.draw.vertex_array = vertex_array.handle; + state.draw.vertex_buffer = vertex_buffer.handle; state.draw.uniform_buffer = 0; state.Apply(); @@ -258,13 +276,13 @@ void RendererOpenGL::InitOpenGLObjects() { glEnableVertexAttribArray(attrib_tex_coord); // Allocate textures for each screen - for (auto& texture : textures) { - glGenTextures(1, &texture.handle); + for (auto& screen_info : screen_infos) { + screen_info.texture.resource.Create(); // Allocation of storage is deferred until the first frame, when we // know the framebuffer size. - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = screen_info.texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); @@ -273,6 +291,8 @@ void RendererOpenGL::InitOpenGLObjects() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + screen_info.display_texture = screen_info.texture.resource.handle; } state.texture_units[0].texture_2d = 0; @@ -327,30 +347,38 @@ void RendererOpenGL::ConfigureFramebufferTexture(TextureInfo& texture, UNIMPLEMENTED(); } - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = texture.resource.handle; state.Apply(); glActiveTexture(GL_TEXTURE0); glTexImage2D(GL_TEXTURE_2D, 0, internal_format, texture.width, texture.height, 0, texture.gl_format, texture.gl_type, nullptr); + + state.texture_units[0].texture_2d = 0; + state.Apply(); } /** * Draws a single texture to the emulator window, rotating the texture to correct for the 3DS's LCD rotation. */ -void RendererOpenGL::DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h) { +void RendererOpenGL::DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h) { + auto& texcoords = screen_info.display_texcoords; + std::array<ScreenRectVertex, 4> vertices = {{ - ScreenRectVertex(x, y, 1.f, 0.f), - ScreenRectVertex(x+w, y, 1.f, 1.f), - ScreenRectVertex(x, y+h, 0.f, 0.f), - ScreenRectVertex(x+w, y+h, 0.f, 1.f), + ScreenRectVertex(x, y, texcoords.bottom, texcoords.left), + ScreenRectVertex(x+w, y, texcoords.bottom, texcoords.right), + ScreenRectVertex(x, y+h, texcoords.top, texcoords.left), + ScreenRectVertex(x+w, y+h, texcoords.top, texcoords.right), }}; - state.texture_units[0].texture_2d = texture.handle; + state.texture_units[0].texture_2d = screen_info.display_texture; state.Apply(); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + state.texture_units[0].texture_2d = 0; + state.Apply(); } /** @@ -362,9 +390,6 @@ void RendererOpenGL::DrawScreens() { glViewport(0, 0, layout.width, layout.height); glClear(GL_COLOR_BUFFER_BIT); - state.draw.shader_program = program_id; - state.Apply(); - // Set projection matrix std::array<GLfloat, 3 * 2> ortho_matrix = MakeOrthographicMatrix((float)layout.width, (float)layout.height); @@ -374,9 +399,9 @@ void RendererOpenGL::DrawScreens() { glActiveTexture(GL_TEXTURE0); glUniform1i(uniform_color_texture, 0); - DrawSingleScreenRotated(textures[0], (float)layout.top_screen.left, (float)layout.top_screen.top, + DrawSingleScreenRotated(screen_infos[0], (float)layout.top_screen.left, (float)layout.top_screen.top, (float)layout.top_screen.GetWidth(), (float)layout.top_screen.GetHeight()); - DrawSingleScreenRotated(textures[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, + DrawSingleScreenRotated(screen_infos[1], (float)layout.bottom_screen.left,(float)layout.bottom_screen.top, (float)layout.bottom_screen.GetWidth(), (float)layout.bottom_screen.GetHeight()); m_current_frame++; diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index fe4d142a5..00e1044ab 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -8,13 +8,34 @@ #include <glad/glad.h> +#include "common/common_types.h" +#include "common/math_util.h" + #include "core/hw/gpu.h" #include "video_core/renderer_base.h" +#include "video_core/renderer_opengl/gl_resource_manager.h" #include "video_core/renderer_opengl/gl_state.h" class EmuWindow; +/// Structure used for storing information about the textures for each 3DS screen +struct TextureInfo { + OGLTexture resource; + GLsizei width; + GLsizei height; + GPU::Regs::PixelFormat format; + GLenum gl_format; + GLenum gl_type; +}; + +/// Structure used for storing information about the display target for each 3DS screen +struct ScreenInfo { + GLuint display_texture; + MathUtil::Rectangle<float> display_texcoords; + TextureInfo texture; +}; + class RendererOpenGL : public RendererBase { public: @@ -37,26 +58,16 @@ public: void ShutDown() override; private: - /// Structure used for storing information about the textures for each 3DS screen - struct TextureInfo { - GLuint handle; - GLsizei width; - GLsizei height; - GPU::Regs::PixelFormat format; - GLenum gl_format; - GLenum gl_type; - }; - void InitOpenGLObjects(); void ConfigureFramebufferTexture(TextureInfo& texture, const GPU::Regs::FramebufferConfig& framebuffer); void DrawScreens(); - void DrawSingleScreenRotated(const TextureInfo& texture, float x, float y, float w, float h); + void DrawSingleScreenRotated(const ScreenInfo& screen_info, float x, float y, float w, float h); void UpdateFramerate(); - // Loads framebuffer from emulated memory into the active OpenGL texture. - void LoadFBToActiveGLTexture(const GPU::Regs::FramebufferConfig& framebuffer, - const TextureInfo& texture); + // Loads framebuffer from emulated memory into the display information structure + void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer, + ScreenInfo& screen_info); // Fills active OpenGL texture with the given RGB color. void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, const TextureInfo& texture); @@ -69,10 +80,10 @@ private: OpenGLState state; // OpenGL object IDs - GLuint vertex_array_handle; - GLuint vertex_buffer_handle; - GLuint program_id; - std::array<TextureInfo, 2> textures; ///< Textures for top and bottom screens respectively + OGLVertexArray vertex_array; + OGLBuffer vertex_buffer; + OGLShader shader; + std::array<ScreenInfo, 2> screen_infos; ///< Display information for top and bottom screens respectively // Shader uniform location indices GLuint uniform_modelview_matrix; GLuint uniform_color_texture; diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 509558fc0..65dcc9156 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp @@ -2,63 +2,53 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <memory> +#include <atomic> +#include <cmath> +#include <cstring> #include <unordered_map> +#include <utility> #include <boost/range/algorithm/fill.hpp> +#include "common/bit_field.h" #include "common/hash.h" -#include "common/make_unique.h" +#include "common/logging/log.h" #include "common/microprofile.h" -#include "common/profiler.h" -#include "video_core/debug_utils/debug_utils.h" #include "video_core/pica.h" #include "video_core/pica_state.h" -#include "video_core/video_core.h" - -#include "shader.h" -#include "shader_interpreter.h" +#include "video_core/shader/shader.h" +#include "video_core/shader/shader_interpreter.h" #ifdef ARCHITECTURE_x86_64 -#include "shader_jit_x64.h" +#include "video_core/shader/shader_jit_x64.h" #endif // ARCHITECTURE_x86_64 +#include "video_core/video_core.h" + namespace Pica { namespace Shader { #ifdef ARCHITECTURE_x86_64 -static std::unordered_map<u64, CompiledShader*> shader_map; -static JitCompiler jit; -static CompiledShader* jit_shader; - -static void ClearCache() { - shader_map.clear(); - jit.Clear(); - LOG_INFO(HW_GPU, "Shader JIT cache cleared"); -} +static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; +static const JitShader* jit_shader; #endif // ARCHITECTURE_x86_64 -void Setup(UnitState<false>& state) { +void Setup() { #ifdef ARCHITECTURE_x86_64 if (VideoCore::g_shader_jit_enabled) { u64 cache_key = (Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^ - Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data)) ^ - g_state.regs.vs.main_offset); + Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data))); auto iter = shader_map.find(cache_key); if (iter != shader_map.end()) { - jit_shader = iter->second; + jit_shader = iter->second.get(); } else { - // Check if remaining JIT code space is enough for at least one more (massive) shader - if (jit.GetSpaceLeft() < jit_shader_size) { - // If not, clear the cache of all previously compiled shaders - ClearCache(); - } - - jit_shader = jit.Compile(); - shader_map.emplace(cache_key, jit_shader); + auto shader = std::make_unique<JitShader>(); + shader->Compile(); + jit_shader = shader.get(); + shader_map[cache_key] = std::move(shader); } } #endif // ARCHITECTURE_x86_64 @@ -66,17 +56,15 @@ void Setup(UnitState<false>& state) { void Shutdown() { #ifdef ARCHITECTURE_x86_64 - ClearCache(); + shader_map.clear(); #endif // ARCHITECTURE_x86_64 } -static Common::Profiling::TimingCategory shader_category("Vertex Shader"); MICROPROFILE_DEFINE(GPU_VertexShader, "GPU", "Vertex Shader", MP_RGB(50, 50, 240)); OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes) { auto& config = g_state.regs.vs; - Common::Profiling::ScopeTimer timer(shader_category); MICROPROFILE_SCOPE(GPU_VertexShader); state.program_counter = config.main_offset; @@ -86,31 +74,15 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr // Setup input register table const auto& attribute_register_map = config.input_register_map; - // TODO: Instead of this cumbersome logic, just load the input data directly like - // for (int attr = 0; attr < num_attributes; ++attr) { input_attr[0] = state.registers.input[attribute_register_map.attribute0_register]; } - if (num_attributes > 0) state.registers.input[attribute_register_map.attribute0_register] = input.attr[0]; - if (num_attributes > 1) state.registers.input[attribute_register_map.attribute1_register] = input.attr[1]; - if (num_attributes > 2) state.registers.input[attribute_register_map.attribute2_register] = input.attr[2]; - if (num_attributes > 3) state.registers.input[attribute_register_map.attribute3_register] = input.attr[3]; - if (num_attributes > 4) state.registers.input[attribute_register_map.attribute4_register] = input.attr[4]; - if (num_attributes > 5) state.registers.input[attribute_register_map.attribute5_register] = input.attr[5]; - if (num_attributes > 6) state.registers.input[attribute_register_map.attribute6_register] = input.attr[6]; - if (num_attributes > 7) state.registers.input[attribute_register_map.attribute7_register] = input.attr[7]; - if (num_attributes > 8) state.registers.input[attribute_register_map.attribute8_register] = input.attr[8]; - if (num_attributes > 9) state.registers.input[attribute_register_map.attribute9_register] = input.attr[9]; - if (num_attributes > 10) state.registers.input[attribute_register_map.attribute10_register] = input.attr[10]; - if (num_attributes > 11) state.registers.input[attribute_register_map.attribute11_register] = input.attr[11]; - if (num_attributes > 12) state.registers.input[attribute_register_map.attribute12_register] = input.attr[12]; - if (num_attributes > 13) state.registers.input[attribute_register_map.attribute13_register] = input.attr[13]; - if (num_attributes > 14) state.registers.input[attribute_register_map.attribute14_register] = input.attr[14]; - if (num_attributes > 15) state.registers.input[attribute_register_map.attribute15_register] = input.attr[15]; + for (unsigned i = 0; i < num_attributes; i++) + state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; state.conditional_code[0] = false; state.conditional_code[1] = false; #ifdef ARCHITECTURE_x86_64 if (VideoCore::g_shader_jit_enabled) - jit_shader(&state.registers); + jit_shader->Run(&state.registers, g_state.regs.vs.main_offset); else RunInterpreter(state); #else @@ -121,15 +93,23 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr OutputVertex ret; // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to // figure out what those circumstances are and enable the remaining outputs then. - for (int i = 0; i < 7; ++i) { - const auto& output_register_map = g_state.regs.vs_output_attributes[i]; // TODO: Don't hardcode VS here + unsigned index = 0; + for (unsigned i = 0; i < 7; ++i) { + + if (index >= g_state.regs.vs_output_total) + break; + + if ((g_state.regs.vs.output_mask & (1 << i)) == 0) + continue; + + const auto& output_register_map = g_state.regs.vs_output_attributes[index]; // TODO: Don't hardcode VS here u32 semantics[4] = { output_register_map.map_x, output_register_map.map_y, output_register_map.map_z, output_register_map.map_w }; - for (int comp = 0; comp < 4; ++comp) { + for (unsigned comp = 0; comp < 4; ++comp) { float24* out = ((float24*)&ret) + semantics[comp]; if (semantics[comp] != Regs::VSOutputAttributes::INVALID) { *out = state.registers.output[i][comp]; @@ -139,10 +119,12 @@ OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attr memset(out, 0, sizeof(*out)); } } + + index++; } // The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation - for (int i = 0; i < 4; ++i) { + for (unsigned i = 0; i < 4; ++i) { ret.color[i] = float24::FromFloat32( std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f)); } @@ -170,22 +152,8 @@ DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes, c float24 dummy_register; boost::fill(state.registers.input, &dummy_register); - if (num_attributes > 0) state.registers.input[attribute_register_map.attribute0_register] = &input.attr[0].x; - if (num_attributes > 1) state.registers.input[attribute_register_map.attribute1_register] = &input.attr[1].x; - if (num_attributes > 2) state.registers.input[attribute_register_map.attribute2_register] = &input.attr[2].x; - if (num_attributes > 3) state.registers.input[attribute_register_map.attribute3_register] = &input.attr[3].x; - if (num_attributes > 4) state.registers.input[attribute_register_map.attribute4_register] = &input.attr[4].x; - if (num_attributes > 5) state.registers.input[attribute_register_map.attribute5_register] = &input.attr[5].x; - if (num_attributes > 6) state.registers.input[attribute_register_map.attribute6_register] = &input.attr[6].x; - if (num_attributes > 7) state.registers.input[attribute_register_map.attribute7_register] = &input.attr[7].x; - if (num_attributes > 8) state.registers.input[attribute_register_map.attribute8_register] = &input.attr[8].x; - if (num_attributes > 9) state.registers.input[attribute_register_map.attribute9_register] = &input.attr[9].x; - if (num_attributes > 10) state.registers.input[attribute_register_map.attribute10_register] = &input.attr[10].x; - if (num_attributes > 11) state.registers.input[attribute_register_map.attribute11_register] = &input.attr[11].x; - if (num_attributes > 12) state.registers.input[attribute_register_map.attribute12_register] = &input.attr[12].x; - if (num_attributes > 13) state.registers.input[attribute_register_map.attribute13_register] = &input.attr[13].x; - if (num_attributes > 14) state.registers.input[attribute_register_map.attribute14_register] = &input.attr[14].x; - if (num_attributes > 15) state.registers.input[attribute_register_map.attribute15_register] = &input.attr[15].x; + for (unsigned i = 0; i < num_attributes; i++) + state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i]; state.conditional_code[0] = false; state.conditional_code[1] = false; diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index 7af8f1fa1..56b83bfeb 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h @@ -4,17 +4,23 @@ #pragma once +#include <array> +#include <cstddef> +#include <memory> +#include <type_traits> #include <vector> #include <boost/container/static_vector.hpp> -#include <nihstro/shader_binary.h> +#include <nihstro/shader_bytecode.h> +#include "common/assert.h" #include "common/common_funcs.h" #include "common/common_types.h" #include "common/vector_math.h" #include "video_core/pica.h" +#include "video_core/pica_types.h" using nihstro::RegisterType; using nihstro::SourceRegister; @@ -25,7 +31,7 @@ namespace Pica { namespace Shader { struct InputVertex { - Math::Vec4<float24> attr[16]; + alignas(16) Math::Vec4<float24> attr[16]; }; struct OutputVertex { @@ -339,9 +345,8 @@ struct UnitState { /** * Performs any shader unit setup that only needs to happen once per shader (as opposed to once per * vertex, which would happen within the `Run` function). - * @param state Shader unit state, must be setup per shader and per shader unit */ -void Setup(UnitState<false>& state); +void Setup(); /// Performs any cleanup when the emulator is shutdown void Shutdown(); diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index 9b978583e..7710f7fbc 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp @@ -2,12 +2,20 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <array> +#include <cmath> #include <numeric> + #include <nihstro/shader_bytecode.h> -#include "common/file_util.h" -#include "video_core/pica.h" +#include "common/assert.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/vector_math.h" + #include "video_core/pica_state.h" +#include "video_core/pica_types.h" #include "video_core/shader/shader.h" #include "video_core/shader/shader_interpreter.h" diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h index 294bca50e..6048cdf3a 100644 --- a/src/video_core/shader/shader_interpreter.h +++ b/src/video_core/shader/shader_interpreter.h @@ -4,12 +4,12 @@ #pragma once -#include "video_core/shader/shader.h" - namespace Pica { namespace Shader { +template <bool Debug> struct UnitState; + template<bool Debug> void RunInterpreter(UnitState<Debug>& state); diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp index dffe051ef..99f6c51eb 100644 --- a/src/video_core/shader/shader_jit_x64.cpp +++ b/src/video_core/shader/shader_jit_x64.cpp @@ -2,8 +2,16 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <smmintrin.h> +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <xmmintrin.h> +#include <nihstro/shader_bytecode.h> + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/vector_math.h" #include "common/x64/abi.h" #include "common/x64/cpu_detect.h" #include "common/x64/emitter.h" @@ -12,6 +20,7 @@ #include "shader_jit_x64.h" #include "video_core/pica_state.h" +#include "video_core/pica_types.h" namespace Pica { @@ -19,73 +28,73 @@ namespace Shader { using namespace Gen; -typedef void (JitCompiler::*JitFunction)(Instruction instr); +typedef void (JitShader::*JitFunction)(Instruction instr); const JitFunction instr_table[64] = { - &JitCompiler::Compile_ADD, // add - &JitCompiler::Compile_DP3, // dp3 - &JitCompiler::Compile_DP4, // dp4 - &JitCompiler::Compile_DPH, // dph + &JitShader::Compile_ADD, // add + &JitShader::Compile_DP3, // dp3 + &JitShader::Compile_DP4, // dp4 + &JitShader::Compile_DPH, // dph nullptr, // unknown - &JitCompiler::Compile_EX2, // ex2 - &JitCompiler::Compile_LG2, // lg2 + &JitShader::Compile_EX2, // ex2 + &JitShader::Compile_LG2, // lg2 nullptr, // unknown - &JitCompiler::Compile_MUL, // mul - &JitCompiler::Compile_SGE, // sge - &JitCompiler::Compile_SLT, // slt - &JitCompiler::Compile_FLR, // flr - &JitCompiler::Compile_MAX, // max - &JitCompiler::Compile_MIN, // min - &JitCompiler::Compile_RCP, // rcp - &JitCompiler::Compile_RSQ, // rsq + &JitShader::Compile_MUL, // mul + &JitShader::Compile_SGE, // sge + &JitShader::Compile_SLT, // slt + &JitShader::Compile_FLR, // flr + &JitShader::Compile_MAX, // max + &JitShader::Compile_MIN, // min + &JitShader::Compile_RCP, // rcp + &JitShader::Compile_RSQ, // rsq nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_MOVA, // mova - &JitCompiler::Compile_MOV, // mov + &JitShader::Compile_MOVA, // mova + &JitShader::Compile_MOV, // mov nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_DPH, // dphi + &JitShader::Compile_DPH, // dphi nullptr, // unknown - &JitCompiler::Compile_SGE, // sgei - &JitCompiler::Compile_SLT, // slti + &JitShader::Compile_SGE, // sgei + &JitShader::Compile_SLT, // slti nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown nullptr, // unknown - &JitCompiler::Compile_NOP, // nop - &JitCompiler::Compile_END, // end + &JitShader::Compile_NOP, // nop + &JitShader::Compile_END, // end nullptr, // break - &JitCompiler::Compile_CALL, // call - &JitCompiler::Compile_CALLC, // callc - &JitCompiler::Compile_CALLU, // callu - &JitCompiler::Compile_IF, // ifu - &JitCompiler::Compile_IF, // ifc - &JitCompiler::Compile_LOOP, // loop + &JitShader::Compile_CALL, // call + &JitShader::Compile_CALLC, // callc + &JitShader::Compile_CALLU, // callu + &JitShader::Compile_IF, // ifu + &JitShader::Compile_IF, // ifc + &JitShader::Compile_LOOP, // loop nullptr, // emit nullptr, // sete - &JitCompiler::Compile_JMP, // jmpc - &JitCompiler::Compile_JMP, // jmpu - &JitCompiler::Compile_CMP, // cmp - &JitCompiler::Compile_CMP, // cmp - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // madi - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad - &JitCompiler::Compile_MAD, // mad + &JitShader::Compile_JMP, // jmpc + &JitShader::Compile_JMP, // jmpu + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_CMP, // cmp + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // madi + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad + &JitShader::Compile_MAD, // mad }; // The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can @@ -138,13 +147,32 @@ static const u8 NO_SRC_REG_SWIZZLE = 0x1b; static const u8 NO_DEST_REG_MASK = 0xf; /** + * Get the vertex shader instruction for a given offset in the current shader program + * @param offset Offset in the current shader program of the instruction + * @return Instruction at the specified offset + */ +static Instruction GetVertexShaderInstruction(size_t offset) { + return { g_state.vs.program_code[offset] }; +} + +static void LogCritical(const char* msg) { + LOG_CRITICAL(HW_GPU, "%s", msg); +} + +void JitShader::Compile_Assert(bool condition, const char* msg) { + if (!condition) { + ABI_CallFunctionP(reinterpret_cast<const void*>(LogCritical), const_cast<char*>(msg)); + } +} + +/** * Loads and swizzles a source register into the specified XMM register. * @param instr VS instruction, used for determining how to load the source register * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3) * @param src_reg SourceRegister object corresponding to the source register to load * @param dest Destination XMM register to store the loaded, swizzled source register */ -void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { +void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, X64Reg dest) { X64Reg src_ptr; size_t src_offset; @@ -216,7 +244,7 @@ void JitCompiler::Compile_SwizzleSrc(Instruction instr, unsigned src_num, Source } } -void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { +void JitShader::Compile_DestEnable(Instruction instr,X64Reg src) { DestRegister dest; unsigned operand_desc_id; if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD || @@ -263,7 +291,7 @@ void JitCompiler::Compile_DestEnable(Instruction instr,X64Reg src) { } } -void JitCompiler::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { +void JitShader::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen::X64Reg scratch) { MOVAPS(scratch, R(src1)); CMPPS(scratch, R(src2), CMP_ORD); @@ -276,7 +304,7 @@ void JitCompiler::Compile_SanitizedMul(Gen::X64Reg src1, Gen::X64Reg src2, Gen:: ANDPS(src1, R(scratch)); } -void JitCompiler::Compile_EvaluateCondition(Instruction instr) { +void JitShader::Compile_EvaluateCondition(Instruction instr) { // Note: NXOR is used below to check for equality switch (instr.flow_control.op) { case Instruction::FlowControlType::Or: @@ -307,23 +335,23 @@ void JitCompiler::Compile_EvaluateCondition(Instruction instr) { } } -void JitCompiler::Compile_UniformCondition(Instruction instr) { +void JitShader::Compile_UniformCondition(Instruction instr) { int offset = offsetof(decltype(g_state.vs.uniforms), b) + (instr.flow_control.bool_uniform_id * sizeof(bool)); CMP(sizeof(bool) * 8, MDisp(UNIFORMS, offset), Imm8(0)); } -BitSet32 JitCompiler::PersistentCallerSavedRegs() { +BitSet32 JitShader::PersistentCallerSavedRegs() { return persistent_regs & ABI_ALL_CALLER_SAVED; } -void JitCompiler::Compile_ADD(Instruction instr) { +void JitShader::Compile_ADD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); ADDPS(SRC1, R(SRC2)); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DP3(Instruction instr) { +void JitShader::Compile_DP3(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); @@ -342,7 +370,7 @@ void JitCompiler::Compile_DP3(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DP4(Instruction instr) { +void JitShader::Compile_DP4(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); @@ -359,7 +387,7 @@ void JitCompiler::Compile_DP4(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_DPH(Instruction instr) { +void JitShader::Compile_DPH(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -391,7 +419,7 @@ void JitCompiler::Compile_DPH(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_EX2(Instruction instr) { +void JitShader::Compile_EX2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); MOVSS(XMM0, R(SRC1)); @@ -404,7 +432,7 @@ void JitCompiler::Compile_EX2(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_LG2(Instruction instr) { +void JitShader::Compile_LG2(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); MOVSS(XMM0, R(SRC1)); @@ -417,14 +445,14 @@ void JitCompiler::Compile_LG2(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MUL(Instruction instr) { +void JitShader::Compile_MUL(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); Compile_SanitizedMul(SRC1, SRC2, SCRATCH); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_SGE(Instruction instr) { +void JitShader::Compile_SGE(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -439,7 +467,7 @@ void JitCompiler::Compile_SGE(Instruction instr) { Compile_DestEnable(instr, SRC2); } -void JitCompiler::Compile_SLT(Instruction instr) { +void JitShader::Compile_SLT(Instruction instr) { if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) { Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2); @@ -454,7 +482,7 @@ void JitCompiler::Compile_SLT(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_FLR(Instruction instr) { +void JitShader::Compile_FLR(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); if (Common::GetCPUCaps().sse4_1) { @@ -467,7 +495,7 @@ void JitCompiler::Compile_FLR(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MAX(Instruction instr) { +void JitShader::Compile_MAX(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. @@ -475,7 +503,7 @@ void JitCompiler::Compile_MAX(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MIN(Instruction instr) { +void JitShader::Compile_MIN(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2); // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned. @@ -483,7 +511,7 @@ void JitCompiler::Compile_MIN(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_MOVA(Instruction instr) { +void JitShader::Compile_MOVA(Instruction instr) { SwizzlePattern swiz = { g_state.vs.swizzle_data[instr.common.operand_desc_id] }; if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) { @@ -528,12 +556,12 @@ void JitCompiler::Compile_MOVA(Instruction instr) { } } -void JitCompiler::Compile_MOV(Instruction instr) { +void JitShader::Compile_MOV(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_RCP(Instruction instr) { +void JitShader::Compile_RCP(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica @@ -544,7 +572,7 @@ void JitCompiler::Compile_RCP(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_RSQ(Instruction instr) { +void JitShader::Compile_RSQ(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1); // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica @@ -555,36 +583,41 @@ void JitCompiler::Compile_RSQ(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_NOP(Instruction instr) { +void JitShader::Compile_NOP(Instruction instr) { } -void JitCompiler::Compile_END(Instruction instr) { +void JitShader::Compile_END(Instruction instr) { ABI_PopRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); RET(); } -void JitCompiler::Compile_CALL(Instruction instr) { - unsigned offset = instr.flow_control.dest_offset; - while (offset < (instr.flow_control.dest_offset + instr.flow_control.num_instructions)) { - Compile_NextInstr(&offset); - } +void JitShader::Compile_CALL(Instruction instr) { + // Push offset of the return + PUSH(64, Imm32(instr.flow_control.dest_offset + instr.flow_control.num_instructions)); + + // Call the subroutine + FixupBranch b = CALL(); + fixup_branches.push_back({ b, instr.flow_control.dest_offset }); + + // Skip over the return offset that's on the stack + ADD(64, R(RSP), Imm32(8)); } -void JitCompiler::Compile_CALLC(Instruction instr) { +void JitShader::Compile_CALLC(Instruction instr) { Compile_EvaluateCondition(instr); FixupBranch b = J_CC(CC_Z, true); Compile_CALL(instr); SetJumpTarget(b); } -void JitCompiler::Compile_CALLU(Instruction instr) { +void JitShader::Compile_CALLU(Instruction instr) { Compile_UniformCondition(instr); FixupBranch b = J_CC(CC_Z, true); Compile_CALL(instr); SetJumpTarget(b); } -void JitCompiler::Compile_CMP(Instruction instr) { +void JitShader::Compile_CMP(Instruction instr) { using Op = Instruction::Common::CompareOpType::Op; Op op_x = instr.common.compare_op.x; Op op_y = instr.common.compare_op.y; @@ -627,7 +660,7 @@ void JitCompiler::Compile_CMP(Instruction instr) { SHR(64, R(COND1), Imm8(63)); } -void JitCompiler::Compile_MAD(Instruction instr) { +void JitShader::Compile_MAD(Instruction instr) { Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1); if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) { @@ -644,9 +677,8 @@ void JitCompiler::Compile_MAD(Instruction instr) { Compile_DestEnable(instr, SRC1); } -void JitCompiler::Compile_IF(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards if-statements (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); +void JitShader::Compile_IF(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards if-statements not supported"); // Evaluate the "IF" condition if (instr.opcode.Value() == OpCode::Id::IFU) { @@ -676,10 +708,9 @@ void JitCompiler::Compile_IF(Instruction instr) { SetJumpTarget(b2); } -void JitCompiler::Compile_LOOP(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards loops (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); - ASSERT_MSG(!looping, "Nested loops not supported"); +void JitShader::Compile_LOOP(Instruction instr) { + Compile_Assert(instr.flow_control.dest_offset >= program_counter, "Backwards loops not supported"); + Compile_Assert(!looping, "Nested loops not supported"); looping = true; @@ -705,10 +736,7 @@ void JitCompiler::Compile_LOOP(Instruction instr) { looping = false; } -void JitCompiler::Compile_JMP(Instruction instr) { - ASSERT_MSG(instr.flow_control.dest_offset > *offset_ptr, "Backwards jumps (%d -> %d) not supported", - *offset_ptr, instr.flow_control.dest_offset.Value()); - +void JitShader::Compile_JMP(Instruction instr) { if (instr.opcode.Value() == OpCode::Id::JMPC) Compile_EvaluateCondition(instr); else if (instr.opcode.Value() == OpCode::Id::JMPU) @@ -718,30 +746,38 @@ void JitCompiler::Compile_JMP(Instruction instr) { bool inverted_condition = (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1); + FixupBranch b = J_CC(inverted_condition ? CC_Z : CC_NZ, true); + fixup_branches.push_back({ b, instr.flow_control.dest_offset }); +} - Compile_Block(instr.flow_control.dest_offset); +void JitShader::Compile_Block(unsigned end) { + while (program_counter < end) { + Compile_NextInstr(); + } +} + +void JitShader::Compile_Return() { + // Peek return offset on the stack and check if we're at that offset + MOV(64, R(RAX), MDisp(RSP, 8)); + CMP(32, R(RAX), Imm32(program_counter)); + // If so, jump back to before CALL + FixupBranch b = J_CC(CC_NZ, true); + RET(); SetJumpTarget(b); } -void JitCompiler::Compile_Block(unsigned end) { - // Save current offset pointer - unsigned* prev_offset_ptr = offset_ptr; - unsigned offset = *prev_offset_ptr; +void JitShader::Compile_NextInstr() { + if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) { + Compile_Return(); + } - while (offset < end) - Compile_NextInstr(&offset); + ASSERT_MSG(code_ptr[program_counter] == nullptr, "Tried to compile already compiled shader location!"); + code_ptr[program_counter] = GetCodePtr(); - // Restore current offset pointer - offset_ptr = prev_offset_ptr; - *offset_ptr = offset; -} + Instruction instr = GetVertexShaderInstruction(program_counter++); -void JitCompiler::Compile_NextInstr(unsigned* offset) { - offset_ptr = offset; - - Instruction instr = *(Instruction*)&g_state.vs.program_code[(*offset_ptr)++]; OpCode::Id opcode = instr.opcode.Value(); auto instr_func = instr_table[static_cast<unsigned>(opcode)]; @@ -755,9 +791,37 @@ void JitCompiler::Compile_NextInstr(unsigned* offset) { } } -CompiledShader* JitCompiler::Compile() { - const u8* start = GetCodePtr(); - unsigned offset = g_state.regs.vs.main_offset; +void JitShader::FindReturnOffsets() { + return_offsets.clear(); + + for (size_t offset = 0; offset < g_state.vs.program_code.size(); ++offset) { + Instruction instr = GetVertexShaderInstruction(offset); + + switch (instr.opcode.Value()) { + case OpCode::Id::CALL: + case OpCode::Id::CALLC: + case OpCode::Id::CALLU: + return_offsets.push_back(instr.flow_control.dest_offset + instr.flow_control.num_instructions); + break; + default: + break; + } + } + + // Sort for efficient binary search later + std::sort(return_offsets.begin(), return_offsets.end()); +} + +void JitShader::Compile() { + // Reset flow control state + program = (CompiledShader*)GetCodePtr(); + program_counter = 0; + looping = false; + code_ptr.fill(nullptr); + fixup_branches.clear(); + + // Find all `CALL` instructions and identify return locations + FindReturnOffsets(); // The stack pointer is 8 modulo 16 at the entry of a procedure ABI_PushRegistersAndAdjustStack(ABI_ALL_CALLEE_SAVED, 8); @@ -780,21 +844,31 @@ CompiledShader* JitCompiler::Compile() { MOV(PTRBITS, R(RAX), ImmPtr(&neg)); MOVAPS(NEGBIT, MatR(RAX)); - looping = false; + // Jump to start of the shader program + JMPptr(R(ABI_PARAM2)); + + // Compile entire program + Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size())); - while (offset < g_state.vs.program_code.size()) { - Compile_NextInstr(&offset); + // Set the target for any incomplete branches now that the entire shader program has been emitted + for (const auto& branch : fixup_branches) { + SetJumpTarget(branch.first, code_ptr[branch.second]); } - return (CompiledShader*)start; -} + // Free memory that's no longer needed + return_offsets.clear(); + return_offsets.shrink_to_fit(); + fixup_branches.clear(); + fixup_branches.shrink_to_fit(); + + uintptr_t size = reinterpret_cast<uintptr_t>(GetCodePtr()) - reinterpret_cast<uintptr_t>(program); + ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!"); -JitCompiler::JitCompiler() { - AllocCodeSpace(jit_cache_size); + LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size); } -void JitCompiler::Clear() { - ClearCodeSpace(); +JitShader::JitShader() { + AllocCodeSpace(MAX_SHADER_SIZE); } } // namespace Shader diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h index 5357c964b..30aa7ff30 100644 --- a/src/video_core/shader/shader_jit_x64.h +++ b/src/video_core/shader/shader_jit_x64.h @@ -4,11 +4,17 @@ #pragma once +#include <array> +#include <cstddef> +#include <utility> +#include <vector> + #include <nihstro/shader_bytecode.h> +#include "common/bit_set.h" +#include "common/common_types.h" #include "common/x64/emitter.h" -#include "video_core/pica.h" #include "video_core/shader/shader.h" using nihstro::Instruction; @@ -19,24 +25,22 @@ namespace Pica { namespace Shader { -/// Memory needed to be available to compile the next shader (otherwise, clear the cache) -constexpr size_t jit_shader_size = 1024 * 512; -/// Memory allocated for the JIT code space cache -constexpr size_t jit_cache_size = 1024 * 1024 * 8; - -using CompiledShader = void(void* registers); +/// Memory allocated for each compiled shader (64Kb) +constexpr size_t MAX_SHADER_SIZE = 1024 * 64; /** * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64 * code that can be executed on the host machine directly. */ -class JitCompiler : public Gen::XCodeBlock { +class JitShader : public Gen::XCodeBlock { public: - JitCompiler(); + JitShader(); - CompiledShader* Compile(); + void Run(void* registers, unsigned offset) const { + program(registers, code_ptr[offset]); + } - void Clear(); + void Compile(); void Compile_ADD(Instruction instr); void Compile_DP3(Instruction instr); @@ -66,8 +70,9 @@ public: void Compile_MAD(Instruction instr); private: + void Compile_Block(unsigned end); - void Compile_NextInstr(unsigned* offset); + void Compile_NextInstr(); void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg, Gen::X64Reg dest); void Compile_DestEnable(Instruction instr, Gen::X64Reg dest); @@ -81,13 +86,39 @@ private: void Compile_EvaluateCondition(Instruction instr); void Compile_UniformCondition(Instruction instr); + /** + * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction. + */ + void Compile_Return(); + BitSet32 PersistentCallerSavedRegs(); - /// Pointer to the variable that stores the current Pica code offset. Used to handle nested code blocks. - unsigned* offset_ptr = nullptr; + /** + * Assertion evaluated at compile-time, but only triggered if executed at runtime. + * @param msg Message to be logged if the assertion fails. + */ + void Compile_Assert(bool condition, const char* msg); + + /** + * Analyzes the entire shader program for `CALL` instructions before emitting any code, + * identifying the locations where a return needs to be inserted. + */ + void FindReturnOffsets(); + + /// Mapping of Pica VS instructions to pointers in the emitted code + std::array<const u8*, 1024> code_ptr; + + /// Offsets in code where a return needs to be inserted + std::vector<unsigned> return_offsets; + + unsigned program_counter = 0; ///< Offset of the next instruction to decode + bool looping = false; ///< True if compiling a loop, used to check for nested loops + + /// Branches that need to be fixed up once the entire shader program is compiled + std::vector<std::pair<Gen::FixupBranch, unsigned>> fixup_branches; - /// Set to true if currently in a loop, used to check for the existence of nested loops - bool looping = false; + using CompiledShader = void(void* registers, const u8* start_addr); + CompiledShader* program = nullptr; }; } // Shader diff --git a/src/video_core/swrasterizer.h b/src/video_core/swrasterizer.h index 9a9a76d7a..0a028b774 100644 --- a/src/video_core/swrasterizer.h +++ b/src/video_core/swrasterizer.h @@ -8,19 +8,23 @@ #include "video_core/rasterizer_interface.h" +namespace Pica { +namespace Shader { +struct OutputVertex; +} +} + namespace VideoCore { class SWRasterizer : public RasterizerInterface { - void InitObjects() override {} - void Reset() override {} void AddTriangle(const Pica::Shader::OutputVertex& v0, const Pica::Shader::OutputVertex& v1, const Pica::Shader::OutputVertex& v2) override; void DrawTriangles() override {} - void FlushFramebuffer() override {} void NotifyPicaRegisterChanged(u32 id) override {} + void FlushAll() override {} void FlushRegion(PAddr addr, u32 size) override {} - void InvalidateRegion(PAddr addr, u32 size) override {} + void FlushAndInvalidateRegion(PAddr addr, u32 size) override {} }; } diff --git a/src/video_core/utils.cpp b/src/video_core/utils.cpp deleted file mode 100644 index 6e1ff5cf4..000000000 --- a/src/video_core/utils.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <cstdio> -#include <cstring> - -#include "video_core/utils.h" - -namespace VideoCore { - -/** - * Dumps a texture to TGA - * @param filename String filename to dump texture to - * @param width Width of texture in pixels - * @param height Height of texture in pixels - * @param raw_data Raw RGBA8 texture data to dump - * @todo This should be moved to some general purpose/common code - */ -void DumpTGA(std::string filename, short width, short height, u8* raw_data) { - TGAHeader hdr = {0, 0, 2, 0, 0, 0, 0, width, height, 24, 0}; - FILE* fout = fopen(filename.c_str(), "wb"); - - fwrite(&hdr, sizeof(TGAHeader), 1, fout); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - putc(raw_data[(3 * (y * width)) + (3 * x) + 0], fout); // b - putc(raw_data[(3 * (y * width)) + (3 * x) + 1], fout); // g - putc(raw_data[(3 * (y * width)) + (3 * x) + 2], fout); // r - } - } - - fclose(fout); -} -} // namespace diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 4fa60a10e..7ce83a055 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h @@ -4,37 +4,10 @@ #pragma once -#include <string> - #include "common/common_types.h" namespace VideoCore { -/// Structure for the TGA texture format (for dumping) -struct TGAHeader { - char idlength; - char colormaptype; - char datatypecode; - short int colormaporigin; - short int colormaplength; - short int x_origin; - short int y_origin; - short width; - short height; - char bitsperpixel; - char imagedescriptor; -}; - -/** - * Dumps a texture to TGA - * @param filename String filename to dump texture to - * @param width Width of texture in pixels - * @param height Height of texture in pixels - * @param raw_data Raw RGBA8 texture data to dump - * @todo This should be moved to some general purpose/common code - */ -void DumpTGA(std::string filename, short width, short height, u8* raw_data); - /** * Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are * arranged in a Z-order curve. More details on the bit manipulation at: diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp new file mode 100644 index 000000000..21ae52949 --- /dev/null +++ b/src/video_core/vertex_loader.cpp @@ -0,0 +1,140 @@ +#include <memory> + +#include <boost/range/algorithm/fill.hpp> + +#include "common/assert.h" +#include "common/alignment.h" +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/logging/log.h" +#include "common/vector_math.h" + +#include "core/memory.h" + +#include "video_core/debug_utils/debug_utils.h" +#include "video_core/pica.h" +#include "video_core/pica_state.h" +#include "video_core/pica_types.h" +#include "video_core/shader/shader.h" +#include "video_core/vertex_loader.h" + +namespace Pica { + +void VertexLoader::Setup(const Pica::Regs& regs) { + const auto& attribute_config = regs.vertex_attributes; + num_total_attributes = attribute_config.GetNumTotalAttributes(); + + boost::fill(vertex_attribute_sources, 0xdeadbeef); + + for (int i = 0; i < 16; i++) { + vertex_attribute_is_default[i] = attribute_config.IsDefaultAttribute(i); + } + + // Setup attribute data from loaders + for (int loader = 0; loader < 12; ++loader) { + const auto& loader_config = attribute_config.attribute_loaders[loader]; + + u32 offset = 0; + + // TODO: What happens if a loader overwrites a previous one's data? + for (unsigned component = 0; component < loader_config.component_count; ++component) { + if (component >= 12) { + LOG_ERROR(HW_GPU, "Overflow in the vertex attribute loader %u trying to load component %u", loader, component); + continue; + } + + u32 attribute_index = loader_config.GetComponent(component); + if (attribute_index < 12) { + offset = Common::AlignUp(offset, attribute_config.GetElementSizeInBytes(attribute_index)); + vertex_attribute_sources[attribute_index] = loader_config.data_offset + offset; + vertex_attribute_strides[attribute_index] = static_cast<u32>(loader_config.byte_count); + vertex_attribute_formats[attribute_index] = attribute_config.GetFormat(attribute_index); + vertex_attribute_elements[attribute_index] = attribute_config.GetNumElements(attribute_index); + offset += attribute_config.GetStride(attribute_index); + } else if (attribute_index < 16) { + // Attribute ids 12, 13, 14 and 15 signify 4, 8, 12 and 16-byte paddings, respectively + offset = Common::AlignUp(offset, 4); + offset += (attribute_index - 11) * 4; + } else { + UNREACHABLE(); // This is truly unreachable due to the number of bits for each component + } + } + } +} + +void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses) { + for (int i = 0; i < num_total_attributes; ++i) { + if (vertex_attribute_elements[i] != 0) { + // Load per-vertex data from the loader arrays + u32 source_addr = base_address + vertex_attribute_sources[i] + vertex_attribute_strides[i] * vertex; + + if (g_debug_context && Pica::g_debug_context->recorder) { + memory_accesses.AddAccess(source_addr, vertex_attribute_elements[i] * ( + (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::FLOAT) ? 4 + : (vertex_attribute_formats[i] == Regs::VertexAttributeFormat::SHORT) ? 2 : 1)); + } + + switch (vertex_attribute_formats[i]) { + case Regs::VertexAttributeFormat::BYTE: + { + const s8* srcdata = reinterpret_cast<const s8*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + case Regs::VertexAttributeFormat::UBYTE: + { + const u8* srcdata = reinterpret_cast<const u8*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + case Regs::VertexAttributeFormat::SHORT: + { + const s16* srcdata = reinterpret_cast<const s16*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + case Regs::VertexAttributeFormat::FLOAT: + { + const float* srcdata = reinterpret_cast<const float*>(Memory::GetPhysicalPointer(source_addr)); + for (unsigned int comp = 0; comp < vertex_attribute_elements[i]; ++comp) { + input.attr[i][comp] = float24::FromFloat32(srcdata[comp]); + } + break; + } + } + + // Default attribute values set if array elements have < 4 components. This + // is *not* carried over from the default attribute settings even if they're + // enabled for this attribute. + for (unsigned int comp = vertex_attribute_elements[i]; comp < 4; ++comp) { + input.attr[i][comp] = comp == 3 ? float24::FromFloat32(1.0f) : float24::FromFloat32(0.0f); + } + + LOG_TRACE(HW_GPU, "Loaded %d components of attribute %x for vertex %x (index %x) from 0x%08x + 0x%08x + 0x%04x: %f %f %f %f", + vertex_attribute_elements[i], i, vertex, index, + base_address, + vertex_attribute_sources[i], + vertex_attribute_strides[i] * vertex, + input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); + } else if (vertex_attribute_is_default[i]) { + // Load the default attribute if we're configured to do so + input.attr[i] = g_state.vs.default_attributes[i]; + LOG_TRACE(HW_GPU, "Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", + i, vertex, index, + input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(), + input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32()); + } else { + // TODO(yuriks): In this case, no data gets loaded and the vertex + // remains with the last value it had. This isn't currently maintained + // as global state, however, and so won't work in Citra yet. + } + } +} + +} // namespace Pica diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h new file mode 100644 index 000000000..becf5a403 --- /dev/null +++ b/src/video_core/vertex_loader.h @@ -0,0 +1,33 @@ +#pragma once + +#include "common/common_types.h" + +#include "video_core/pica.h" + +namespace Pica { + +namespace DebugUtils { +class MemoryAccessTracker; +} + +namespace Shader { +class InputVertex; +} + +class VertexLoader { +public: + void Setup(const Pica::Regs& regs); + void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses); + + int GetNumTotalAttributes() const { return num_total_attributes; } + +private: + u32 vertex_attribute_sources[16]; + u32 vertex_attribute_strides[16] = {}; + Regs::VertexAttributeFormat vertex_attribute_formats[16] = {}; + u32 vertex_attribute_elements[16] = {}; + bool vertex_attribute_is_default[16]; + int num_total_attributes; +}; + +} // namespace Pica diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index ee5e50df1..c9975876d 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -4,13 +4,8 @@ #include <memory> -#include "common/emu_window.h" -#include "common/make_unique.h" #include "common/logging/log.h" -#include "core/core.h" -#include "core/settings.h" - #include "video_core/pica.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" @@ -26,13 +21,14 @@ std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin std::atomic<bool> g_hw_renderer_enabled; std::atomic<bool> g_shader_jit_enabled; +std::atomic<bool> g_scaled_resolution_enabled; /// Initialize the video core bool Init(EmuWindow* emu_window) { Pica::Init(); g_emu_window = emu_window; - g_renderer = Common::make_unique<RendererOpenGL>(); + g_renderer = std::make_unique<RendererOpenGL>(); g_renderer->SetWindow(g_emu_window); if (g_renderer->Init()) { LOG_DEBUG(Render, "initialized OK"); diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h index bca67fb8c..30267489e 100644 --- a/src/video_core/video_core.h +++ b/src/video_core/video_core.h @@ -36,6 +36,7 @@ extern EmuWindow* g_emu_window; ///< Emu window // TODO: Wrap these in a user settings struct along with any other graphics settings (often set from qt ui) extern std::atomic<bool> g_hw_renderer_enabled; extern std::atomic<bool> g_shader_jit_enabled; +extern std::atomic<bool> g_scaled_resolution_enabled; /// Start the video core void Start(); |
