diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp index a3115a718f..30632dc0dd 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp @@ -42,7 +42,7 @@ struct FormatTuple { GLenum type; }; -static const std::array fb_format_tuples = {{ +static constexpr std::array fb_format_tuples = {{ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8 {GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8 {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1 @@ -50,7 +50,7 @@ static const std::array fb_format_tuples = {{ {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4 }}; -static const std::array depth_format_tuples = {{ +static constexpr std::array depth_format_tuples = {{ {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16 {}, {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24 @@ -78,93 +78,10 @@ constexpr auto RangeFromInterval(Map& map, const Interval& interval) { return boost::make_iterator_range(map.equal_range(interval)); } -static bool FillSurface(const Surface& surface, const u8* fill_data, - const MathUtil::Rectangle& fill_rect) { - OpenGLState state = OpenGLState::GetCurState(); - - OpenGLState prev_state = state; - SCOPE_EXIT({ prev_state.Apply(); }); - - state.ResetTexture(surface->texture.handle); - - state.scissor.enabled = true; - state.scissor.x = static_cast(fill_rect.left); - state.scissor.y = static_cast(fill_rect.bottom); - state.scissor.width = static_cast(fill_rect.GetWidth()); - state.scissor.height = static_cast(fill_rect.GetHeight()); - - state.draw.draw_framebuffer = transfer_framebuffers[1].handle; - state.Apply(); - - if (surface->type == SurfaceType::Color || surface->type == SurfaceType::Texture) { - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - surface->texture.handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, - 0); - - Pica::Texture::TextureInfo tex_info{}; - tex_info.format = static_cast(surface->pixel_format); - Math::Vec4 color = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info); - - std::array color_values = {color.x / 255.f, color.y / 255.f, color.z / 255.f, - color.w / 255.f}; - - state.color_mask.red_enabled = GL_TRUE; - state.color_mask.green_enabled = GL_TRUE; - state.color_mask.blue_enabled = GL_TRUE; - state.color_mask.alpha_enabled = GL_TRUE; - state.Apply(); - glClearBufferfv(GL_COLOR, 0, &color_values[0]); - } else if (surface->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, - surface->texture.handle, 0); - glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - - u32 value_32bit = 0; - GLfloat value_float; - - if (surface->pixel_format == SurfaceParams::PixelFormat::D16) { - std::memcpy(&value_32bit, fill_data, 2); - value_float = value_32bit / 65535.0f; // 2^16 - 1 - } else if (surface->pixel_format == SurfaceParams::PixelFormat::D24) { - std::memcpy(&value_32bit, fill_data, 3); - value_float = value_32bit / 16777215.0f; // 2^24 - 1 - } - - state.depth.write_mask = GL_TRUE; - state.Apply(); - glClearBufferfv(GL_DEPTH, 0, &value_float); - } else if (surface->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, - surface->texture.handle, 0); - - u32 value_32bit; - std::memcpy(&value_32bit, fill_data, 4); - - GLfloat value_float = (value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 - GLint value_int = (value_32bit >> 24); - - state.depth.write_mask = GL_TRUE; - state.stencil.write_mask = -1; - state.Apply(); - glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int); - } - return true; -} - -RasterizerCacheOpenGL::RasterizerCacheOpenGL() { - transfer_framebuffers[0].Create(); - transfer_framebuffers[1].Create(); -} - -RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { - FlushAll(); - while (!surface_cache.empty()) - UnregisterSurface(*surface_cache.begin()->second.begin()); - transfer_framebuffers[0].Release(); - transfer_framebuffers[1].Release(); +static u16 GetResolutionScaleFactor() { + return !Settings::values.resolution_factor + ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio() + : Settings::values.resolution_factor; } template @@ -292,6 +209,30 @@ static constexpr std::array gl MortonCopy // 17 }; +// Allocate an uninitialized texture of appropriate size and format for the surface +static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, + u32 height) { + 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); + + glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0, + format_tuple.format, 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(); +} + static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle& src_rect, GLuint dst_tex, const MathUtil::Rectangle& dst_rect, SurfaceType type) { OpenGLState state = OpenGLState::GetCurState(); @@ -352,6 +293,82 @@ static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle& src_rec return true; } +static bool FillSurface(const Surface& surface, const u8* fill_data, + const MathUtil::Rectangle& fill_rect) { + OpenGLState state = OpenGLState::GetCurState(); + + OpenGLState prev_state = state; + SCOPE_EXIT({ prev_state.Apply(); }); + + state.ResetTexture(surface->texture.handle); + + state.scissor.enabled = true; + state.scissor.x = static_cast(fill_rect.left); + state.scissor.y = static_cast(fill_rect.bottom); + state.scissor.width = static_cast(fill_rect.GetWidth()); + state.scissor.height = static_cast(fill_rect.GetHeight()); + + state.draw.draw_framebuffer = transfer_framebuffers[1].handle; + state.Apply(); + + if (surface->type == SurfaceType::Color || surface->type == SurfaceType::Texture) { + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + surface->texture.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, + 0); + + Pica::Texture::TextureInfo tex_info{}; + tex_info.format = static_cast(surface->pixel_format); + Math::Vec4 color = Pica::Texture::LookupTexture(fill_data, 0, 0, tex_info); + + std::array color_values = {color.x / 255.f, color.y / 255.f, color.z / 255.f, + color.w / 255.f}; + + state.color_mask.red_enabled = GL_TRUE; + state.color_mask.green_enabled = GL_TRUE; + state.color_mask.blue_enabled = GL_TRUE; + state.color_mask.alpha_enabled = GL_TRUE; + state.Apply(); + glClearBufferfv(GL_COLOR, 0, &color_values[0]); + } else if (surface->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, + surface->texture.handle, 0); + glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + + u32 value_32bit = 0; + GLfloat value_float; + + if (surface->pixel_format == SurfaceParams::PixelFormat::D16) { + std::memcpy(&value_32bit, fill_data, 2); + value_float = value_32bit / 65535.0f; // 2^16 - 1 + } else if (surface->pixel_format == SurfaceParams::PixelFormat::D24) { + std::memcpy(&value_32bit, fill_data, 3); + value_float = value_32bit / 16777215.0f; // 2^24 - 1 + } + + state.depth.write_mask = GL_TRUE; + state.Apply(); + glClearBufferfv(GL_DEPTH, 0, &value_float); + } else if (surface->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, + surface->texture.handle, 0); + + u32 value_32bit; + std::memcpy(&value_32bit, fill_data, 4); + + GLfloat value_float = (value_32bit & 0xFFFFFF) / 16777215.0f; // 2^24 - 1 + GLint value_int = (value_32bit >> 24); + + state.depth.write_mask = GL_TRUE; + state.stencil.write_mask = -1; + state.Apply(); + glClearBufferfi(GL_DEPTH_STENCIL, 0, value_float, value_int); + } + return true; +} + SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const { SurfaceParams params = *this; @@ -386,10 +403,6 @@ SurfaceInterval SurfaceParams::GetSubRectInterval(MathUtil::Rectangle unsca return {}; } - if (unscaled_rect.bottom > unscaled_rect.top) { - std::swap(unscaled_rect.top, unscaled_rect.bottom); - } - if (is_tiled) { unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8; unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8; @@ -414,14 +427,15 @@ MathUtil::Rectangle SurfaceParams::GetSubRect(const SurfaceParams& sub_surf if (is_tiled) { const int x0 = (begin_pixel_index % (stride * 8)) / 8; const int y0 = (begin_pixel_index / (stride * 8)) * 8; + // Top to bottom return MathUtil::Rectangle(x0, height - y0, x0 + sub_surface.width, - height - (y0 + sub_surface.height)); // Top to bottom + height - (y0 + sub_surface.height)); } const int x0 = begin_pixel_index % stride; const int y0 = begin_pixel_index / stride; - return MathUtil::Rectangle(x0, y0 + sub_surface.height, x0 + sub_surface.width, - y0); // Bottom to top + // Bottom to top + return MathUtil::Rectangle(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0); } MathUtil::Rectangle SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const { @@ -482,13 +496,14 @@ bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const { return false; const u32 begin_pixel_index = PixelsInBytes(texcopy_params.addr - addr); - const int x0 = begin_pixel_index % stride; - const int y0 = begin_pixel_index / stride; - if (!is_tiled) + if (!is_tiled) { + const int x0 = begin_pixel_index % stride; return ((texcopy_params.height == 1 || PixelsInBytes(texcopy_params.stride) == stride) && x0 + PixelsInBytes(texcopy_params.width) <= stride); + } + const int x0 = (begin_pixel_index % (stride * 8)) / 8; return (PixelsInBytes(texcopy_params.addr - addr) % 64 == 0 && PixelsInBytes(texcopy_params.width) % 64 == 0 && (texcopy_params.height == 1 || PixelsInBytes(texcopy_params.stride) == stride * 8) && @@ -607,41 +622,6 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac UNREACHABLE(); } -bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, - const MathUtil::Rectangle& src_rect, - const Surface& dst_surface, - const MathUtil::Rectangle& dst_rect) { - if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) - return false; - - return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle, - dst_rect, src_surface->type); -} - -// Allocate an uninitialized texture of appropriate size and format for the surface -static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, - u32 height) { - 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); - - glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0, - format_tuple.format, 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_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { ASSERT(type != SurfaceType::Fill); @@ -680,7 +660,7 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { tex_info.SetDefaultStride(); tex_info.physical_address = addr; - const auto load_interval = SurfaceInterval(load_start, load_end); + const SurfaceInterval load_interval(load_start, load_end); const auto rect = GetSubRect(FromInterval(load_interval)); ASSERT(FromInterval(load_interval).GetInterval() == load_interval); @@ -699,338 +679,6 @@ void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { } } -Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, - bool load_if_create) { - if (params.addr == 0 || params.height * params.width == 0) { - return nullptr; - } - - ASSERT(params.width == params.stride); // Use GetSurfaceSubRect instead - - // Check for an exact match in existing surfaces - Surface surface = - FindMatch(surface_cache, params, match_res_scale); - - if (surface == nullptr) { - u16 target_res_scale = params.res_scale; - if (match_res_scale != ScaleMatch::Exact) { - // This surface may have a subrect of another surface with a higher res_scale, find it - // to adjust our params - SurfaceParams find_params = params; - Surface expandable = FindMatch( - surface_cache, find_params, match_res_scale); - if (expandable != nullptr && expandable->res_scale > target_res_scale) { - target_res_scale = expandable->res_scale; - } - // Keep res_scale when reinterpreting d24s8 -> rgba8 - if (params.pixel_format == PixelFormat::RGBA8) { - find_params.pixel_format = PixelFormat::D24S8; - expandable = FindMatch( - surface_cache, find_params, match_res_scale); - if (expandable != nullptr && expandable->res_scale > target_res_scale) { - target_res_scale = expandable->res_scale; - } - } - } - SurfaceParams new_params = params; - new_params.res_scale = target_res_scale; - surface = CreateSurface(new_params); - RegisterSurface(surface); - } - - if (load_if_create) { - ValidateSurface(surface, params.addr, params.size); - } - - return surface; -} - -SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params, - ScaleMatch match_res_scale, - bool load_if_create) { - if (params.addr == 0 || params.height * params.width == 0) { - return {nullptr, {}}; - } - - // Attempt to find encompassing surface - Surface surface = FindMatch(surface_cache, params, - match_res_scale); - - // Check if FindMatch failed because of res scaling - // If that's the case create a new surface with - // the dimensions of the lower res_scale surface - // to suggest it should not be used again - if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) { - surface = FindMatch(surface_cache, params, - ScaleMatch::Ignore); - if (surface != nullptr) { - ASSERT(surface->res_scale < params.res_scale); - SurfaceParams new_params = *surface; - new_params.res_scale = params.res_scale; - - surface = CreateSurface(new_params); - RegisterSurface(surface); - } - } - - // Check for a surface we can expand before creating a new one - if (surface == nullptr) { - surface = FindMatch(surface_cache, params, - match_res_scale); - if (surface != nullptr) { - SurfaceParams new_params = *surface; - new_params.addr = std::min(params.addr, surface->addr); - new_params.end = std::max(params.end, surface->end); - new_params.size = new_params.end - new_params.addr; - new_params.height = new_params.size / params.BytesInPixels(params.stride); - ASSERT(new_params.size % params.BytesInPixels(params.stride) == 0); - - Surface new_surface = CreateSurface(new_params); - DuplicateSurface(surface, new_surface); - - // Delete the expanded surface, this can't be done safely yet - // because it may still be in use - remove_surfaces.emplace(surface); - - surface = new_surface; - RegisterSurface(new_surface); - } - } - - // No subrect found - create and return a new surface - if (surface == nullptr) { - SurfaceParams new_params = params; - new_params.width = params.stride; // Can't have gaps in a surface - new_params.UpdateParams(); - // GetSurface will create the new surface and possibly adjust res_scale if necessary - surface = GetSurface(new_params, match_res_scale, load_if_create); - } else if (load_if_create) { - ValidateSurface(surface, params.addr, params.size); - } - - return {surface, surface->GetScaledSubRect(params)}; -} - -Surface RasterizerCacheOpenGL::GetTextureSurface( - const Pica::TexturingRegs::FullTextureConfig& config) { - Pica::Texture::TextureInfo info = - Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); - - SurfaceParams params; - params.addr = info.physical_address; - params.width = info.width; - params.height = info.height; - params.is_tiled = true; - params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format); - params.UpdateParams(); - return GetSurface(params, ScaleMatch::Ignore, true); -} - -static u16 GetResolutionScaleFactor() { - return !Settings::values.resolution_factor - ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio() - : Settings::values.resolution_factor; -} - -SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( - bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle& viewport_rect) { - const auto& regs = Pica::g_state.regs; - const auto& config = regs.framebuffer.framebuffer; - - // update resolution_scale_factor and reset cache if changed - static u16 resolution_scale_factor = GetResolutionScaleFactor(); - if (resolution_scale_factor != GetResolutionScaleFactor()) { - resolution_scale_factor = GetResolutionScaleFactor(); - FlushAll(); - while (!surface_cache.empty()) - UnregisterSurface(*surface_cache.begin()->second.begin()); - } - - MathUtil::Rectangle viewport_clamped{ - static_cast( - MathUtil::Clamp(viewport_rect.left, 0, static_cast(config.GetWidth()))), - static_cast( - MathUtil::Clamp(viewport_rect.top, 0, static_cast(config.GetHeight()))), - static_cast( - MathUtil::Clamp(viewport_rect.right, 0, static_cast(config.GetWidth()))), - static_cast( - MathUtil::Clamp(viewport_rect.bottom, 0, static_cast(config.GetHeight())))}; - - // get color and depth surfaces - SurfaceParams color_params; - color_params.is_tiled = true; - color_params.res_scale = resolution_scale_factor; - color_params.width = config.GetWidth(); - color_params.height = config.GetHeight(); - SurfaceParams depth_params = color_params; - - color_params.addr = config.GetColorBufferPhysicalAddress(); - color_params.pixel_format = SurfaceParams::PixelFormatFromColorFormat(config.color_format); - color_params.UpdateParams(); - - depth_params.addr = config.GetDepthBufferPhysicalAddress(); - depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format); - depth_params.UpdateParams(); - - auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped); - auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped); - - // Make sur that framebuffers don't overlap if both color and depth are being used - if (using_color_fb && using_depth_fb && - boost::icl::length(color_vp_interval & depth_vp_interval)) { - LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " - "overlapping framebuffers not supported!"); - using_depth_fb = false; - } - - MathUtil::Rectangle color_rect{}; - Surface color_surface = nullptr; - if (using_color_fb) - std::tie(color_surface, color_rect) = - GetSurfaceSubRect(color_params, ScaleMatch::Exact, false); - - MathUtil::Rectangle depth_rect{}; - Surface depth_surface = nullptr; - if (using_depth_fb) - std::tie(depth_surface, depth_rect) = - GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false); - - MathUtil::Rectangle fb_rect{}; - if (color_surface != nullptr && depth_surface != nullptr) { - fb_rect = color_rect; - // Color and Depth surfaces must have the same dimensions and offsets - if (color_rect.bottom != depth_rect.bottom || - color_surface->height != depth_surface->height) { - color_surface = GetSurface(color_params, ScaleMatch::Exact, false); - depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false); - fb_rect = color_surface->GetScaledRect(); - } - } else if (color_surface != nullptr) { - fb_rect = color_rect; - } else if (depth_surface != nullptr) { - fb_rect = depth_rect; - } - ASSERT(!fb_rect.left && fb_rect.right == config.GetWidth() * resolution_scale_factor); - - if (color_surface != nullptr) { - ValidateSurface(color_surface, boost::icl::first(color_vp_interval), - boost::icl::length(color_vp_interval)); - } - if (depth_surface != nullptr) { - ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval), - boost::icl::length(depth_vp_interval)); - } - - return {color_surface, depth_surface, fb_rect}; -} - -SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) { - MathUtil::Rectangle rect{}; - - Surface match_surface = FindMatch( - surface_cache, params, ScaleMatch::Ignore); - - if (match_surface != nullptr) { - ValidateSurface(match_surface, params.addr, params.size); - - SurfaceParams match_subrect = params; - match_subrect.width = match_surface->PixelsInBytes(params.width); - match_subrect.stride = match_surface->PixelsInBytes(params.stride); - - if (match_surface->is_tiled) { - match_subrect.width /= 8; - match_subrect.stride /= 8; - match_subrect.height *= 8; - } - - rect = match_surface->GetScaledSubRect(match_subrect); - } - - return {match_surface, rect}; -} - -Surface RasterizerCacheOpenGL::GetFillSurface(const GPU::Regs::MemoryFillConfig& config) { - Surface new_surface = std::make_shared(); - - new_surface->addr = config.GetStartAddress(); - new_surface->end = config.GetEndAddress(); - new_surface->size = new_surface->end - new_surface->addr; - new_surface->type = SurfaceType::Fill; - new_surface->res_scale = std::numeric_limits::max(); - std::memcpy(&new_surface->fill_data[0], &config.value_32bit, 4); - if (config.fill_32bit) { - new_surface->fill_size = 4; - } else if (config.fill_24bit) { - new_surface->fill_size = 3; - } else { - new_surface->fill_size = 2; - } - - RegisterSurface(new_surface); - return new_surface; -} - -void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface, - const Surface& dest_surface) { - ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end); - - BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface, - dest_surface->GetScaledSubRect(*src_surface)); - - dest_surface->invalid_regions -= src_surface->GetInterval(); - dest_surface->invalid_regions += src_surface->invalid_regions; - - SurfaceRegions regions; - for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) { - if (pair.second == src_surface) { - regions += pair.first; - } - } - for (auto& interval : regions) { - dirty_regions.set({interval, dest_surface}); - } -} - -void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, u32 size) { - if (size == 0) - return; - - const auto validate_interval = SurfaceInterval(addr, addr + size); - - if (surface->type == SurfaceType::Fill) { - // Sanity check, fill surfaces will always be valid when used - ASSERT(surface->IsRegionValid(validate_interval)); - return; - } - - const auto validate_regions = surface->invalid_regions.find(validate_interval); - - for (;;) { - const auto it = validate_regions.begin(); - if (it == surface->invalid_regions.end()) - break; - - const auto interval = *it & validate_interval; - // Look for a valid surface to copy from - SurfaceParams params = surface->FromInterval(interval); - - Surface copy_surface = - FindMatch(surface_cache, params, ScaleMatch::Ignore, interval); - if (copy_surface != nullptr) { - SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); - CopySurface(copy_surface, surface, copy_interval); - validate_regions.erase(interval); - continue; - } - - // Load data from 3DS memory - FlushRegion(params.addr, params.size); - surface->LoadGLBuffer(params.addr, params.end); - surface->UploadGLTexture(surface->GetSubRect(params)); - validate_regions.erase(interval) - } -} - MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) { u8* const dst_buffer = Memory::GetPhysicalPointer(addr); @@ -1299,13 +947,368 @@ Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params return match_surface; } +RasterizerCacheOpenGL::RasterizerCacheOpenGL() { + transfer_framebuffers[0].Create(); + transfer_framebuffers[1].Create(); +} + +RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); + transfer_framebuffers[0].Release(); + transfer_framebuffers[1].Release(); +} + +bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, + const MathUtil::Rectangle& src_rect, + const Surface& dst_surface, + const MathUtil::Rectangle& dst_rect) { + if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) + return false; + + return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle, + dst_rect, src_surface->type); +} + +Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, + bool load_if_create) { + if (params.addr == 0 || params.height * params.width == 0) { + return nullptr; + } + + ASSERT(params.width == params.stride); // Use GetSurfaceSubRect instead + + // Check for an exact match in existing surfaces + Surface surface = + FindMatch(surface_cache, params, match_res_scale); + + if (surface == nullptr) { + u16 target_res_scale = params.res_scale; + if (match_res_scale != ScaleMatch::Exact) { + // This surface may have a subrect of another surface with a higher res_scale, find it + // to adjust our params + SurfaceParams find_params = params; + Surface expandable = FindMatch( + surface_cache, find_params, match_res_scale); + if (expandable != nullptr && expandable->res_scale > target_res_scale) { + target_res_scale = expandable->res_scale; + } + // Keep res_scale when reinterpreting d24s8 -> rgba8 + if (params.pixel_format == PixelFormat::RGBA8) { + find_params.pixel_format = PixelFormat::D24S8; + expandable = FindMatch( + surface_cache, find_params, match_res_scale); + if (expandable != nullptr && expandable->res_scale > target_res_scale) { + target_res_scale = expandable->res_scale; + } + } + } + SurfaceParams new_params = params; + new_params.res_scale = target_res_scale; + surface = CreateSurface(new_params); + RegisterSurface(surface); + } + + if (load_if_create) { + ValidateSurface(surface, params.addr, params.size); + } + + return surface; +} + +SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params, + ScaleMatch match_res_scale, + bool load_if_create) { + if (params.addr == 0 || params.height * params.width == 0) { + return {nullptr, {}}; + } + + // Attempt to find encompassing surface + Surface surface = FindMatch(surface_cache, params, + match_res_scale); + + // Check if FindMatch failed because of res scaling + // If that's the case create a new surface with + // the dimensions of the lower res_scale surface + // to suggest it should not be used again + if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) { + surface = FindMatch(surface_cache, params, + ScaleMatch::Ignore); + if (surface != nullptr) { + ASSERT(surface->res_scale < params.res_scale); + SurfaceParams new_params = *surface; + new_params.res_scale = params.res_scale; + + surface = CreateSurface(new_params); + RegisterSurface(surface); + } + } + + // Check for a surface we can expand before creating a new one + if (surface == nullptr) { + surface = FindMatch(surface_cache, params, + match_res_scale); + if (surface != nullptr) { + SurfaceParams new_params = *surface; + new_params.addr = std::min(params.addr, surface->addr); + new_params.end = std::max(params.end, surface->end); + new_params.size = new_params.end - new_params.addr; + new_params.height = new_params.size / params.BytesInPixels(params.stride); + ASSERT(new_params.size % params.BytesInPixels(params.stride) == 0); + + Surface new_surface = CreateSurface(new_params); + DuplicateSurface(surface, new_surface); + + // Delete the expanded surface, this can't be done safely yet + // because it may still be in use + remove_surfaces.emplace(surface); + + surface = new_surface; + RegisterSurface(new_surface); + } + } + + // No subrect found - create and return a new surface + if (surface == nullptr) { + SurfaceParams new_params = params; + new_params.width = params.stride; // Can't have gaps in a surface + new_params.UpdateParams(); + // GetSurface will create the new surface and possibly adjust res_scale if necessary + surface = GetSurface(new_params, match_res_scale, load_if_create); + } else if (load_if_create) { + ValidateSurface(surface, params.addr, params.size); + } + + return {surface, surface->GetScaledSubRect(params)}; +} + +Surface RasterizerCacheOpenGL::GetTextureSurface( + const Pica::TexturingRegs::FullTextureConfig& config) { + Pica::Texture::TextureInfo info = + Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format); + + SurfaceParams params; + params.addr = info.physical_address; + params.width = info.width; + params.height = info.height; + params.is_tiled = true; + params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format); + params.UpdateParams(); + return GetSurface(params, ScaleMatch::Ignore, true); +} + +SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( + bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle& viewport_rect) { + const auto& regs = Pica::g_state.regs; + const auto& config = regs.framebuffer.framebuffer; + + // update resolution_scale_factor and reset cache if changed + static u16 resolution_scale_factor = GetResolutionScaleFactor(); + if (resolution_scale_factor != GetResolutionScaleFactor()) { + resolution_scale_factor = GetResolutionScaleFactor(); + FlushAll(); + while (!surface_cache.empty()) + UnregisterSurface(*surface_cache.begin()->second.begin()); + } + + MathUtil::Rectangle viewport_clamped{ + static_cast( + MathUtil::Clamp(viewport_rect.left, 0, static_cast(config.GetWidth()))), + static_cast( + MathUtil::Clamp(viewport_rect.top, 0, static_cast(config.GetHeight()))), + static_cast( + MathUtil::Clamp(viewport_rect.right, 0, static_cast(config.GetWidth()))), + static_cast( + MathUtil::Clamp(viewport_rect.bottom, 0, static_cast(config.GetHeight())))}; + + // get color and depth surfaces + SurfaceParams color_params; + color_params.is_tiled = true; + color_params.res_scale = resolution_scale_factor; + color_params.width = config.GetWidth(); + color_params.height = config.GetHeight(); + SurfaceParams depth_params = color_params; + + color_params.addr = config.GetColorBufferPhysicalAddress(); + color_params.pixel_format = SurfaceParams::PixelFormatFromColorFormat(config.color_format); + color_params.UpdateParams(); + + depth_params.addr = config.GetDepthBufferPhysicalAddress(); + depth_params.pixel_format = SurfaceParams::PixelFormatFromDepthFormat(config.depth_format); + depth_params.UpdateParams(); + + auto color_vp_interval = color_params.GetSubRectInterval(viewport_clamped); + auto depth_vp_interval = depth_params.GetSubRectInterval(viewport_clamped); + + // Make sur that framebuffers don't overlap if both color and depth are being used + if (using_color_fb && using_depth_fb && + boost::icl::length(color_vp_interval & depth_vp_interval)) { + LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; " + "overlapping framebuffers not supported!"); + using_depth_fb = false; + } + + MathUtil::Rectangle color_rect{}; + Surface color_surface = nullptr; + if (using_color_fb) + std::tie(color_surface, color_rect) = + GetSurfaceSubRect(color_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle depth_rect{}; + Surface depth_surface = nullptr; + if (using_depth_fb) + std::tie(depth_surface, depth_rect) = + GetSurfaceSubRect(depth_params, ScaleMatch::Exact, false); + + MathUtil::Rectangle fb_rect{}; + if (color_surface != nullptr && depth_surface != nullptr) { + fb_rect = color_rect; + // Color and Depth surfaces must have the same dimensions and offsets + if (color_rect.bottom != depth_rect.bottom || + color_surface->height != depth_surface->height) { + color_surface = GetSurface(color_params, ScaleMatch::Exact, false); + depth_surface = GetSurface(depth_params, ScaleMatch::Exact, false); + fb_rect = color_surface->GetScaledRect(); + } + } else if (color_surface != nullptr) { + fb_rect = color_rect; + } else if (depth_surface != nullptr) { + fb_rect = depth_rect; + } + ASSERT(!fb_rect.left && fb_rect.right == config.GetWidth() * resolution_scale_factor); + + if (color_surface != nullptr) { + ValidateSurface(color_surface, boost::icl::first(color_vp_interval), + boost::icl::length(color_vp_interval)); + } + if (depth_surface != nullptr) { + ValidateSurface(depth_surface, boost::icl::first(depth_vp_interval), + boost::icl::length(depth_vp_interval)); + } + + return {color_surface, depth_surface, fb_rect}; +} + +Surface RasterizerCacheOpenGL::GetFillSurface(const GPU::Regs::MemoryFillConfig& config) { + Surface new_surface = std::make_shared(); + + new_surface->addr = config.GetStartAddress(); + new_surface->end = config.GetEndAddress(); + new_surface->size = new_surface->end - new_surface->addr; + new_surface->type = SurfaceType::Fill; + new_surface->res_scale = std::numeric_limits::max(); + std::memcpy(&new_surface->fill_data[0], &config.value_32bit, 4); + if (config.fill_32bit) { + new_surface->fill_size = 4; + } else if (config.fill_24bit) { + new_surface->fill_size = 3; + } else { + new_surface->fill_size = 2; + } + + RegisterSurface(new_surface); + return new_surface; +} + +SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) { + MathUtil::Rectangle rect{}; + + Surface match_surface = FindMatch( + surface_cache, params, ScaleMatch::Ignore); + + if (match_surface != nullptr) { + ValidateSurface(match_surface, params.addr, params.size); + + SurfaceParams match_subrect = params; + match_subrect.width = match_surface->PixelsInBytes(params.width); + match_subrect.stride = match_surface->PixelsInBytes(params.stride); + + if (match_surface->is_tiled) { + match_subrect.width /= 8; + match_subrect.stride /= 8; + match_subrect.height *= 8; + } + + rect = match_surface->GetScaledSubRect(match_subrect); + } + + return {match_surface, rect}; +} + +void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface, + const Surface& dest_surface) { + ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end); + + BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface, + dest_surface->GetScaledSubRect(*src_surface)); + + dest_surface->invalid_regions -= src_surface->GetInterval(); + dest_surface->invalid_regions += src_surface->invalid_regions; + + SurfaceRegions regions; + for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) { + if (pair.second == src_surface) { + regions += pair.first; + } + } + for (auto& interval : regions) { + dirty_regions.set({interval, dest_surface}); + } +} + +void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, u32 size) { + if (size == 0) + return; + + const SurfaceInterval validate_interval(addr, addr + size); + + if (surface->type == SurfaceType::Fill) { + // Sanity check, fill surfaces will always be valid when used + ASSERT(surface->IsRegionValid(validate_interval)); + return; + } + + const auto validate_regions = surface->invalid_regions.find(validate_interval); + + for (;;) { + const auto it = validate_regions.begin(); + if (it == surface->invalid_regions.end()) + break; + + const auto interval = *it & validate_interval; + // Look for a valid surface to copy from + SurfaceParams params = surface->FromInterval(interval); + + Surface copy_surface = + FindMatch(surface_cache, params, ScaleMatch::Ignore, interval); + if (copy_surface != nullptr) { + SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); + CopySurface(copy_surface, surface, copy_interval); + validate_regions.erase(interval); + continue; + } + + // Load data from 3DS memory + FlushRegion(params.addr, params.size); + surface->LoadGLBuffer(params.addr, params.end); + surface->UploadGLTexture(surface->GetSubRect(params)); + validate_regions.erase(interval) + } +} + void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surface) { if (size == 0) return; - const auto flush_interval = SurfaceInterval(addr, addr + size); + const SurfaceInterval flush_interval(addr, addr + size); + SurfaceRegions flushed_intervals; + for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) { - const auto interval = pair.first & flush_interval; + const auto interval = size <= 8 + ? // this most likely comes from the cpu, flush the entire region + pair.first + : pair.first & flush_interval; auto& surface = pair.second; if (flush_surface != nullptr && surface != flush_surface) @@ -1319,10 +1322,10 @@ void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surf surface->DownloadGLTexture(surface->GetSubRect(params)); } surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval)); + flushed_intervals += interval; } - // Reset dirty regions - dirty_regions.erase(flush_interval); + dirty_regions -= flushed_intervals; } void RasterizerCacheOpenGL::FlushAll() { @@ -1333,7 +1336,7 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface if (size == 0) return; - const auto invalid_interval = SurfaceInterval(addr, addr + size); + const SurfaceInterval invalid_interval(addr, addr + size); if (region_owner != nullptr) { ASSERT(region_owner->type != SurfaceType::Texture); @@ -1347,6 +1350,14 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface if (cached_surface == region_owner) continue; + // If cpu is invalidating this region we want to remove it + // to (likely) mark the memory pages as uncached + if (region_owner == nullptr && size <= 8) { + FlushRegion(cached_surface->addr, cached_surface->size, cached_surface); + remove_surfaces.emplace(cached_surface); + continue; + } + const auto interval = cached_surface->GetInterval() & invalid_interval; cached_surface->invalid_regions.insert(interval);