diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/video_core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 1362 | ||||
| -rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer_cache.h | 350 |
3 files changed, 1714 insertions, 0 deletions
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 7b26fe180..92951a7e1 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -13,6 +13,8 @@ add_library(video_core STATIC | |||
| 13 | memory_manager.h | 13 | memory_manager.h |
| 14 | renderer_base.cpp | 14 | renderer_base.cpp |
| 15 | renderer_base.h | 15 | renderer_base.h |
| 16 | renderer_opengl/gl_rasterizer_cache.cpp | ||
| 17 | renderer_opengl/gl_rasterizer_cache.h | ||
| 16 | renderer_opengl/gl_resource_manager.h | 18 | renderer_opengl/gl_resource_manager.h |
| 17 | renderer_opengl/gl_shader_util.cpp | 19 | renderer_opengl/gl_shader_util.cpp |
| 18 | renderer_opengl/gl_shader_util.h | 20 | renderer_opengl/gl_shader_util.h |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp new file mode 100644 index 000000000..e481139af --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | |||
| @@ -0,0 +1,1362 @@ | |||
| 1 | // Copyright 2015 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | #include <atomic> | ||
| 7 | #include <cstring> | ||
| 8 | #include <iterator> | ||
| 9 | #include <memory> | ||
| 10 | #include <unordered_set> | ||
| 11 | #include <utility> | ||
| 12 | #include <vector> | ||
| 13 | #include <boost/optional.hpp> | ||
| 14 | #include <boost/range/iterator_range.hpp> | ||
| 15 | #include <glad/glad.h> | ||
| 16 | #include "common/alignment.h" | ||
| 17 | #include "common/bit_field.h" | ||
| 18 | #include "common/color.h" | ||
| 19 | #include "common/logging/log.h" | ||
| 20 | #include "common/math_util.h" | ||
| 21 | #include "common/microprofile.h" | ||
| 22 | #include "common/scope_exit.h" | ||
| 23 | #include "common/vector_math.h" | ||
| 24 | #include "core/frontend/emu_window.h" | ||
| 25 | #include "core/memory.h" | ||
| 26 | #include "core/settings.h" | ||
| 27 | #include "video_core/renderer_opengl/gl_rasterizer_cache.h" | ||
| 28 | #include "video_core/renderer_opengl/gl_state.h" | ||
| 29 | #include "video_core/texture/texture_decode.h" | ||
| 30 | #include "video_core/utils.h" | ||
| 31 | #include "video_core/video_core.h" | ||
| 32 | |||
| 33 | using SurfaceType = SurfaceParams::SurfaceType; | ||
| 34 | using PixelFormat = SurfaceParams::PixelFormat; | ||
| 35 | |||
| 36 | struct FormatTuple { | ||
| 37 | GLint internal_format; | ||
| 38 | GLenum format; | ||
| 39 | GLenum type; | ||
| 40 | }; | ||
| 41 | |||
| 42 | static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{ | ||
| 43 | {GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8 | ||
| 44 | {GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8 | ||
| 45 | {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1 | ||
| 46 | {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565 | ||
| 47 | {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4 | ||
| 48 | }}; | ||
| 49 | |||
| 50 | static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{ | ||
| 51 | {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16 | ||
| 52 | {}, | ||
| 53 | {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24 | ||
| 54 | {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8 | ||
| 55 | }}; | ||
| 56 | |||
| 57 | static constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}; | ||
| 58 | |||
| 59 | static const FormatTuple& GetFormatTuple(PixelFormat pixel_format) { | ||
| 60 | const SurfaceType type = SurfaceParams::GetFormatType(pixel_format); | ||
| 61 | if (type == SurfaceType::Color) { | ||
| 62 | ASSERT(static_cast<size_t>(pixel_format) < fb_format_tuples.size()); | ||
| 63 | return fb_format_tuples[static_cast<unsigned int>(pixel_format)]; | ||
| 64 | } else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) { | ||
| 65 | size_t tuple_idx = static_cast<size_t>(pixel_format) - 14; | ||
| 66 | ASSERT(tuple_idx < depth_format_tuples.size()); | ||
| 67 | return depth_format_tuples[tuple_idx]; | ||
| 68 | } | ||
| 69 | return tex_tuple; | ||
| 70 | } | ||
| 71 | |||
| 72 | template <typename Map, typename Interval> | ||
| 73 | constexpr auto RangeFromInterval(Map& map, const Interval& interval) { | ||
| 74 | return boost::make_iterator_range(map.equal_range(interval)); | ||
| 75 | } | ||
| 76 | |||
| 77 | static u16 GetResolutionScaleFactor() { | ||
| 78 | return static_cast<u16>(!Settings::values.resolution_factor | ||
| 79 | ? VideoCore::g_emu_window->GetFramebufferLayout().GetScalingRatio() | ||
| 80 | : Settings::values.resolution_factor); | ||
| 81 | } | ||
| 82 | |||
| 83 | template <bool morton_to_gl, PixelFormat format> | ||
| 84 | static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) { | ||
| 85 | constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8; | ||
| 86 | constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); | ||
| 87 | for (u32 y = 0; y < 8; ++y) { | ||
| 88 | for (u32 x = 0; x < 8; ++x) { | ||
| 89 | u8* tile_ptr = tile_buffer + VideoCore::MortonInterleave(x, y) * bytes_per_pixel; | ||
| 90 | u8* gl_ptr = gl_buffer + ((7 - y) * stride + x) * gl_bytes_per_pixel; | ||
| 91 | if (morton_to_gl) { | ||
| 92 | if (format == PixelFormat::D24S8) { | ||
| 93 | gl_ptr[0] = tile_ptr[3]; | ||
| 94 | std::memcpy(gl_ptr + 1, tile_ptr, 3); | ||
| 95 | } else { | ||
| 96 | std::memcpy(gl_ptr, tile_ptr, bytes_per_pixel); | ||
| 97 | } | ||
| 98 | } else { | ||
| 99 | if (format == PixelFormat::D24S8) { | ||
| 100 | std::memcpy(tile_ptr, gl_ptr + 1, 3); | ||
| 101 | tile_ptr[3] = gl_ptr[0]; | ||
| 102 | } else { | ||
| 103 | std::memcpy(tile_ptr, gl_ptr, bytes_per_pixel); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | template <bool morton_to_gl, PixelFormat format> | ||
| 111 | static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, PAddr base, PAddr start, PAddr end) { | ||
| 112 | constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / 8; | ||
| 113 | constexpr u32 tile_size = bytes_per_pixel * 64; | ||
| 114 | |||
| 115 | constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format); | ||
| 116 | static_assert(gl_bytes_per_pixel >= bytes_per_pixel, ""); | ||
| 117 | gl_buffer += gl_bytes_per_pixel - bytes_per_pixel; | ||
| 118 | |||
| 119 | const PAddr aligned_down_start = base + Common::AlignDown(start - base, tile_size); | ||
| 120 | const PAddr aligned_start = base + Common::AlignUp(start - base, tile_size); | ||
| 121 | const PAddr aligned_end = base + Common::AlignDown(end - base, tile_size); | ||
| 122 | |||
| 123 | ASSERT(!morton_to_gl || (aligned_start == start && aligned_end == end)); | ||
| 124 | |||
| 125 | const u64 begin_pixel_index = (aligned_down_start - base) / bytes_per_pixel; | ||
| 126 | u32 x = static_cast<u32>((begin_pixel_index % (stride * 8)) / 8); | ||
| 127 | u32 y = static_cast<u32>((begin_pixel_index / (stride * 8)) * 8); | ||
| 128 | |||
| 129 | gl_buffer += ((height - 8 - y) * stride + x) * gl_bytes_per_pixel; | ||
| 130 | |||
| 131 | auto glbuf_next_tile = [&] { | ||
| 132 | x = (x + 8) % stride; | ||
| 133 | gl_buffer += 8 * gl_bytes_per_pixel; | ||
| 134 | if (!x) { | ||
| 135 | y += 8; | ||
| 136 | gl_buffer -= stride * 9 * gl_bytes_per_pixel; | ||
| 137 | } | ||
| 138 | }; | ||
| 139 | |||
| 140 | u8* tile_buffer = Memory::GetPhysicalPointer(start); | ||
| 141 | |||
| 142 | if (start < aligned_start && !morton_to_gl) { | ||
| 143 | std::array<u8, tile_size> tmp_buf; | ||
| 144 | MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer); | ||
| 145 | std::memcpy(tile_buffer, &tmp_buf[start - aligned_down_start], | ||
| 146 | std::min(aligned_start, end) - start); | ||
| 147 | |||
| 148 | tile_buffer += aligned_start - start; | ||
| 149 | glbuf_next_tile(); | ||
| 150 | } | ||
| 151 | |||
| 152 | const u8* const buffer_end = tile_buffer + aligned_end - aligned_start; | ||
| 153 | while (tile_buffer < buffer_end) { | ||
| 154 | MortonCopyTile<morton_to_gl, format>(stride, tile_buffer, gl_buffer); | ||
| 155 | tile_buffer += tile_size; | ||
| 156 | glbuf_next_tile(); | ||
| 157 | } | ||
| 158 | |||
| 159 | if (end > std::max(aligned_start, aligned_end) && !morton_to_gl) { | ||
| 160 | std::array<u8, tile_size> tmp_buf; | ||
| 161 | MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer); | ||
| 162 | std::memcpy(tile_buffer, &tmp_buf[0], end - aligned_end); | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> morton_to_gl_fns = { | ||
| 167 | MortonCopy<true, PixelFormat::RGBA8>, // 0 | ||
| 168 | MortonCopy<true, PixelFormat::RGB8>, // 1 | ||
| 169 | MortonCopy<true, PixelFormat::RGB5A1>, // 2 | ||
| 170 | MortonCopy<true, PixelFormat::RGB565>, // 3 | ||
| 171 | MortonCopy<true, PixelFormat::RGBA4>, // 4 | ||
| 172 | nullptr, | ||
| 173 | nullptr, | ||
| 174 | nullptr, | ||
| 175 | nullptr, | ||
| 176 | nullptr, | ||
| 177 | nullptr, | ||
| 178 | nullptr, | ||
| 179 | nullptr, | ||
| 180 | nullptr, // 5 - 13 | ||
| 181 | MortonCopy<true, PixelFormat::D16>, // 14 | ||
| 182 | nullptr, // 15 | ||
| 183 | MortonCopy<true, PixelFormat::D24>, // 16 | ||
| 184 | MortonCopy<true, PixelFormat::D24S8> // 17 | ||
| 185 | }; | ||
| 186 | |||
| 187 | static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl_to_morton_fns = { | ||
| 188 | MortonCopy<false, PixelFormat::RGBA8>, // 0 | ||
| 189 | MortonCopy<false, PixelFormat::RGB8>, // 1 | ||
| 190 | MortonCopy<false, PixelFormat::RGB5A1>, // 2 | ||
| 191 | MortonCopy<false, PixelFormat::RGB565>, // 3 | ||
| 192 | MortonCopy<false, PixelFormat::RGBA4>, // 4 | ||
| 193 | nullptr, | ||
| 194 | nullptr, | ||
| 195 | nullptr, | ||
| 196 | nullptr, | ||
| 197 | nullptr, | ||
| 198 | nullptr, | ||
| 199 | nullptr, | ||
| 200 | nullptr, | ||
| 201 | nullptr, // 5 - 13 | ||
| 202 | MortonCopy<false, PixelFormat::D16>, // 14 | ||
| 203 | nullptr, // 15 | ||
| 204 | MortonCopy<false, PixelFormat::D24>, // 16 | ||
| 205 | MortonCopy<false, PixelFormat::D24S8> // 17 | ||
| 206 | }; | ||
| 207 | |||
| 208 | // Allocate an uninitialized texture of appropriate size and format for the surface | ||
| 209 | static void AllocateSurfaceTexture(GLuint texture, const FormatTuple& format_tuple, u32 width, | ||
| 210 | u32 height) { | ||
| 211 | OpenGLState cur_state = OpenGLState::GetCurState(); | ||
| 212 | |||
| 213 | // Keep track of previous texture bindings | ||
| 214 | GLuint old_tex = cur_state.texture_units[0].texture_2d; | ||
| 215 | cur_state.texture_units[0].texture_2d = texture; | ||
| 216 | cur_state.Apply(); | ||
| 217 | glActiveTexture(GL_TEXTURE0); | ||
| 218 | |||
| 219 | glTexImage2D(GL_TEXTURE_2D, 0, format_tuple.internal_format, width, height, 0, | ||
| 220 | format_tuple.format, format_tuple.type, nullptr); | ||
| 221 | |||
| 222 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); | ||
| 223 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | ||
| 224 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||
| 225 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||
| 226 | |||
| 227 | // Restore previous texture bindings | ||
| 228 | cur_state.texture_units[0].texture_2d = old_tex; | ||
| 229 | cur_state.Apply(); | ||
| 230 | } | ||
| 231 | |||
| 232 | static bool BlitTextures(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, GLuint dst_tex, | ||
| 233 | const MathUtil::Rectangle<u32>& dst_rect, SurfaceType type, | ||
| 234 | GLuint read_fb_handle, GLuint draw_fb_handle) { | ||
| 235 | OpenGLState state = OpenGLState::GetCurState(); | ||
| 236 | |||
| 237 | OpenGLState prev_state = state; | ||
| 238 | SCOPE_EXIT({ prev_state.Apply(); }); | ||
| 239 | |||
| 240 | // Make sure textures aren't bound to texture units, since going to bind them to framebuffer | ||
| 241 | // components | ||
| 242 | state.ResetTexture(src_tex); | ||
| 243 | state.ResetTexture(dst_tex); | ||
| 244 | |||
| 245 | state.draw.read_framebuffer = read_fb_handle; | ||
| 246 | state.draw.draw_framebuffer = draw_fb_handle; | ||
| 247 | state.Apply(); | ||
| 248 | |||
| 249 | u32 buffers = 0; | ||
| 250 | |||
| 251 | if (type == SurfaceType::Color || type == SurfaceType::Texture) { | ||
| 252 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex, | ||
| 253 | 0); | ||
| 254 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | ||
| 255 | 0); | ||
| 256 | |||
| 257 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, | ||
| 258 | 0); | ||
| 259 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, | ||
| 260 | 0); | ||
| 261 | |||
| 262 | buffers = GL_COLOR_BUFFER_BIT; | ||
| 263 | } else if (type == SurfaceType::Depth) { | ||
| 264 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 265 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0); | ||
| 266 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||
| 267 | |||
| 268 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 269 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0); | ||
| 270 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||
| 271 | |||
| 272 | buffers = GL_DEPTH_BUFFER_BIT; | ||
| 273 | } else if (type == SurfaceType::DepthStencil) { | ||
| 274 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 275 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||
| 276 | src_tex, 0); | ||
| 277 | |||
| 278 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 279 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||
| 280 | dst_tex, 0); | ||
| 281 | |||
| 282 | buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; | ||
| 283 | } | ||
| 284 | |||
| 285 | glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top, dst_rect.left, | ||
| 286 | dst_rect.bottom, dst_rect.right, dst_rect.top, buffers, | ||
| 287 | buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST); | ||
| 288 | |||
| 289 | return true; | ||
| 290 | } | ||
| 291 | |||
| 292 | static bool FillSurface(const Surface& surface, const u8* fill_data, | ||
| 293 | const MathUtil::Rectangle<u32>& fill_rect, GLuint draw_fb_handle) { | ||
| 294 | UNIMPLEMENTED(); | ||
| 295 | return true; | ||
| 296 | } | ||
| 297 | |||
| 298 | SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const { | ||
| 299 | SurfaceParams params = *this; | ||
| 300 | const u32 tiled_size = is_tiled ? 8 : 1; | ||
| 301 | const u64 stride_tiled_bytes = BytesInPixels(stride * tiled_size); | ||
| 302 | PAddr aligned_start = | ||
| 303 | addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes); | ||
| 304 | PAddr aligned_end = | ||
| 305 | addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes); | ||
| 306 | |||
| 307 | if (aligned_end - aligned_start > stride_tiled_bytes) { | ||
| 308 | params.addr = aligned_start; | ||
| 309 | params.height = static_cast<u32>((aligned_end - aligned_start) / BytesInPixels(stride)); | ||
| 310 | } else { | ||
| 311 | // 1 row | ||
| 312 | ASSERT(aligned_end - aligned_start == stride_tiled_bytes); | ||
| 313 | const u64 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1); | ||
| 314 | aligned_start = | ||
| 315 | addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment); | ||
| 316 | aligned_end = | ||
| 317 | addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment); | ||
| 318 | params.addr = aligned_start; | ||
| 319 | params.width = static_cast<u32>(PixelsInBytes(aligned_end - aligned_start) / tiled_size); | ||
| 320 | params.stride = params.width; | ||
| 321 | params.height = tiled_size; | ||
| 322 | } | ||
| 323 | params.UpdateParams(); | ||
| 324 | |||
| 325 | return params; | ||
| 326 | } | ||
| 327 | |||
| 328 | SurfaceInterval SurfaceParams::GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const { | ||
| 329 | if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) { | ||
| 330 | return {}; | ||
| 331 | } | ||
| 332 | |||
| 333 | if (is_tiled) { | ||
| 334 | unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8; | ||
| 335 | unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8; | ||
| 336 | unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8; | ||
| 337 | unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8; | ||
| 338 | } | ||
| 339 | |||
| 340 | const u32 stride_tiled = !is_tiled ? stride : stride * 8; | ||
| 341 | |||
| 342 | const u32 pixel_offset = | ||
| 343 | stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) + | ||
| 344 | unscaled_rect.left; | ||
| 345 | |||
| 346 | const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth(); | ||
| 347 | |||
| 348 | return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)}; | ||
| 349 | } | ||
| 350 | |||
| 351 | MathUtil::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const { | ||
| 352 | const u32 begin_pixel_index = static_cast<u32>(PixelsInBytes(sub_surface.addr - addr)); | ||
| 353 | |||
| 354 | if (is_tiled) { | ||
| 355 | const int x0 = (begin_pixel_index % (stride * 8)) / 8; | ||
| 356 | const int y0 = (begin_pixel_index / (stride * 8)) * 8; | ||
| 357 | // Top to bottom | ||
| 358 | return MathUtil::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width, | ||
| 359 | height - (y0 + sub_surface.height)); | ||
| 360 | } | ||
| 361 | |||
| 362 | const int x0 = begin_pixel_index % stride; | ||
| 363 | const int y0 = begin_pixel_index / stride; | ||
| 364 | // Bottom to top | ||
| 365 | return MathUtil::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0); | ||
| 366 | } | ||
| 367 | |||
| 368 | MathUtil::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const { | ||
| 369 | auto rect = GetSubRect(sub_surface); | ||
| 370 | rect.left = rect.left * res_scale; | ||
| 371 | rect.right = rect.right * res_scale; | ||
| 372 | rect.top = rect.top * res_scale; | ||
| 373 | rect.bottom = rect.bottom * res_scale; | ||
| 374 | return rect; | ||
| 375 | } | ||
| 376 | |||
| 377 | bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const { | ||
| 378 | return std::tie(other_surface.addr, other_surface.width, other_surface.height, | ||
| 379 | other_surface.stride, other_surface.pixel_format, other_surface.is_tiled) == | ||
| 380 | std::tie(addr, width, height, stride, pixel_format, is_tiled) && | ||
| 381 | pixel_format != PixelFormat::Invalid; | ||
| 382 | } | ||
| 383 | |||
| 384 | bool SurfaceParams::CanSubRect(const SurfaceParams& sub_surface) const { | ||
| 385 | return sub_surface.addr >= addr && sub_surface.end <= end && | ||
| 386 | sub_surface.pixel_format == pixel_format && pixel_format != PixelFormat::Invalid && | ||
| 387 | sub_surface.is_tiled == is_tiled && | ||
| 388 | (sub_surface.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && | ||
| 389 | (sub_surface.stride == stride || sub_surface.height <= (is_tiled ? 8u : 1u)) && | ||
| 390 | GetSubRect(sub_surface).left + sub_surface.width <= stride; | ||
| 391 | } | ||
| 392 | |||
| 393 | bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const { | ||
| 394 | return pixel_format != PixelFormat::Invalid && pixel_format == expanded_surface.pixel_format && | ||
| 395 | addr <= expanded_surface.end && expanded_surface.addr <= end && | ||
| 396 | is_tiled == expanded_surface.is_tiled && stride == expanded_surface.stride && | ||
| 397 | (std::max(expanded_surface.addr, addr) - std::min(expanded_surface.addr, addr)) % | ||
| 398 | BytesInPixels(stride * (is_tiled ? 8 : 1)) == | ||
| 399 | 0; | ||
| 400 | } | ||
| 401 | |||
| 402 | bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const { | ||
| 403 | if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr || | ||
| 404 | end < texcopy_params.end) { | ||
| 405 | return false; | ||
| 406 | } | ||
| 407 | if (texcopy_params.width != texcopy_params.stride) { | ||
| 408 | const u32 tile_stride = static_cast<u32>(BytesInPixels(stride * (is_tiled ? 8 : 1))); | ||
| 409 | return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 && | ||
| 410 | texcopy_params.width % BytesInPixels(is_tiled ? 64 : 1) == 0 && | ||
| 411 | (texcopy_params.height == 1 || texcopy_params.stride == tile_stride) && | ||
| 412 | ((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride; | ||
| 413 | } | ||
| 414 | return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval(); | ||
| 415 | } | ||
| 416 | |||
| 417 | bool CachedSurface::CanFill(const SurfaceParams& dest_surface, | ||
| 418 | SurfaceInterval fill_interval) const { | ||
| 419 | if (type == SurfaceType::Fill && IsRegionValid(fill_interval) && | ||
| 420 | boost::icl::first(fill_interval) >= addr && | ||
| 421 | boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range | ||
| 422 | dest_surface.FromInterval(fill_interval).GetInterval() == | ||
| 423 | fill_interval) { // make sure interval is a rectangle in dest surface | ||
| 424 | if (fill_size * 8 != dest_surface.GetFormatBpp()) { | ||
| 425 | // Check if bits repeat for our fill_size | ||
| 426 | const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u); | ||
| 427 | std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel); | ||
| 428 | |||
| 429 | for (u32 i = 0; i < dest_bytes_per_pixel; ++i) | ||
| 430 | std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size); | ||
| 431 | |||
| 432 | for (u32 i = 0; i < fill_size; ++i) | ||
| 433 | if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0], | ||
| 434 | dest_bytes_per_pixel) != 0) | ||
| 435 | return false; | ||
| 436 | |||
| 437 | if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4)) | ||
| 438 | return false; | ||
| 439 | } | ||
| 440 | return true; | ||
| 441 | } | ||
| 442 | return false; | ||
| 443 | } | ||
| 444 | |||
| 445 | bool CachedSurface::CanCopy(const SurfaceParams& dest_surface, | ||
| 446 | SurfaceInterval copy_interval) const { | ||
| 447 | SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval); | ||
| 448 | ASSERT(subrect_params.GetInterval() == copy_interval); | ||
| 449 | if (CanSubRect(subrect_params)) | ||
| 450 | return true; | ||
| 451 | |||
| 452 | if (CanFill(dest_surface, copy_interval)) | ||
| 453 | return true; | ||
| 454 | |||
| 455 | return false; | ||
| 456 | } | ||
| 457 | |||
| 458 | SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const { | ||
| 459 | SurfaceInterval result{}; | ||
| 460 | const auto valid_regions = | ||
| 461 | SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions; | ||
| 462 | for (auto& valid_interval : valid_regions) { | ||
| 463 | const SurfaceInterval aligned_interval{ | ||
| 464 | addr + Common::AlignUp(boost::icl::first(valid_interval) - addr, | ||
| 465 | BytesInPixels(is_tiled ? 8 * 8 : 1)), | ||
| 466 | addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr, | ||
| 467 | BytesInPixels(is_tiled ? 8 * 8 : 1))}; | ||
| 468 | |||
| 469 | if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) || | ||
| 470 | boost::icl::length(aligned_interval) == 0) { | ||
| 471 | continue; | ||
| 472 | } | ||
| 473 | |||
| 474 | // Get the rectangle within aligned_interval | ||
| 475 | const u32 stride_bytes = static_cast<u32>(BytesInPixels(stride)) * (is_tiled ? 8 : 1); | ||
| 476 | SurfaceInterval rect_interval{ | ||
| 477 | addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes), | ||
| 478 | addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes), | ||
| 479 | }; | ||
| 480 | if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) { | ||
| 481 | // 1 row | ||
| 482 | rect_interval = aligned_interval; | ||
| 483 | } else if (boost::icl::length(rect_interval) == 0) { | ||
| 484 | // 2 rows that do not make a rectangle, return the larger one | ||
| 485 | const SurfaceInterval row1{boost::icl::first(aligned_interval), | ||
| 486 | boost::icl::first(rect_interval)}; | ||
| 487 | const SurfaceInterval row2{boost::icl::first(rect_interval), | ||
| 488 | boost::icl::last_next(aligned_interval)}; | ||
| 489 | rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2; | ||
| 490 | } | ||
| 491 | |||
| 492 | if (boost::icl::length(rect_interval) > boost::icl::length(result)) { | ||
| 493 | result = rect_interval; | ||
| 494 | } | ||
| 495 | } | ||
| 496 | return result; | ||
| 497 | } | ||
| 498 | |||
| 499 | void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surface& dst_surface, | ||
| 500 | SurfaceInterval copy_interval) { | ||
| 501 | SurfaceParams subrect_params = dst_surface->FromInterval(copy_interval); | ||
| 502 | ASSERT(subrect_params.GetInterval() == copy_interval); | ||
| 503 | |||
| 504 | ASSERT(src_surface != dst_surface); | ||
| 505 | |||
| 506 | // This is only called when CanCopy is true, no need to run checks here | ||
| 507 | if (src_surface->type == SurfaceType::Fill) { | ||
| 508 | // FillSurface needs a 4 bytes buffer | ||
| 509 | const u64 fill_offset = | ||
| 510 | (boost::icl::first(copy_interval) - src_surface->addr) % src_surface->fill_size; | ||
| 511 | std::array<u8, 4> fill_buffer; | ||
| 512 | |||
| 513 | u64 fill_buff_pos = fill_offset; | ||
| 514 | for (int i : {0, 1, 2, 3}) | ||
| 515 | fill_buffer[i] = src_surface->fill_data[fill_buff_pos++ % src_surface->fill_size]; | ||
| 516 | |||
| 517 | FillSurface(dst_surface, &fill_buffer[0], dst_surface->GetScaledSubRect(subrect_params), | ||
| 518 | draw_framebuffer.handle); | ||
| 519 | return; | ||
| 520 | } | ||
| 521 | if (src_surface->CanSubRect(subrect_params)) { | ||
| 522 | BlitTextures(src_surface->texture.handle, src_surface->GetScaledSubRect(subrect_params), | ||
| 523 | dst_surface->texture.handle, dst_surface->GetScaledSubRect(subrect_params), | ||
| 524 | src_surface->type, read_framebuffer.handle, draw_framebuffer.handle); | ||
| 525 | return; | ||
| 526 | } | ||
| 527 | UNREACHABLE(); | ||
| 528 | } | ||
| 529 | |||
| 530 | MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192)); | ||
| 531 | void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) { | ||
| 532 | ASSERT(type != SurfaceType::Fill); | ||
| 533 | |||
| 534 | const u8* const texture_src_data = Memory::GetPhysicalPointer(addr); | ||
| 535 | if (texture_src_data == nullptr) | ||
| 536 | return; | ||
| 537 | |||
| 538 | if (gl_buffer == nullptr) { | ||
| 539 | gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); | ||
| 540 | gl_buffer.reset(new u8[gl_buffer_size]); | ||
| 541 | } | ||
| 542 | |||
| 543 | // TODO: Should probably be done in ::Memory:: and check for other regions too | ||
| 544 | if (load_start < Memory::VRAM_VADDR_END && load_end > Memory::VRAM_VADDR_END) | ||
| 545 | load_end = Memory::VRAM_VADDR_END; | ||
| 546 | |||
| 547 | if (load_start < Memory::VRAM_VADDR && load_end > Memory::VRAM_VADDR) | ||
| 548 | load_start = Memory::VRAM_VADDR; | ||
| 549 | |||
| 550 | MICROPROFILE_SCOPE(OpenGL_SurfaceLoad); | ||
| 551 | |||
| 552 | ASSERT(load_start >= addr && load_end <= end); | ||
| 553 | const u32 start_offset = load_start - addr; | ||
| 554 | |||
| 555 | if (!is_tiled) { | ||
| 556 | ASSERT(type == SurfaceType::Color); | ||
| 557 | std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset, | ||
| 558 | load_end - load_start); | ||
| 559 | } else { | ||
| 560 | if (type == SurfaceType::Texture) { | ||
| 561 | UNIMPLEMENTED(); | ||
| 562 | } else { | ||
| 563 | morton_to_gl_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr, | ||
| 564 | load_start, load_end); | ||
| 565 | } | ||
| 566 | } | ||
| 567 | } | ||
| 568 | |||
| 569 | MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64)); | ||
| 570 | void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) { | ||
| 571 | u8* const dst_buffer = Memory::GetPhysicalPointer(addr); | ||
| 572 | if (dst_buffer == nullptr) | ||
| 573 | return; | ||
| 574 | |||
| 575 | ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); | ||
| 576 | |||
| 577 | // TODO: Should probably be done in ::Memory:: and check for other regions too | ||
| 578 | // same as loadglbuffer() | ||
| 579 | if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END) | ||
| 580 | flush_end = Memory::VRAM_VADDR_END; | ||
| 581 | |||
| 582 | if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR) | ||
| 583 | flush_start = Memory::VRAM_VADDR; | ||
| 584 | |||
| 585 | MICROPROFILE_SCOPE(OpenGL_SurfaceFlush); | ||
| 586 | |||
| 587 | ASSERT(flush_start >= addr && flush_end <= end); | ||
| 588 | const u64 start_offset = flush_start - addr; | ||
| 589 | const u64 end_offset = flush_end - addr; | ||
| 590 | |||
| 591 | if (type == SurfaceType::Fill) { | ||
| 592 | const u64 coarse_start_offset = start_offset - (start_offset % fill_size); | ||
| 593 | const u64 backup_bytes = start_offset % fill_size; | ||
| 594 | std::array<u8, 4> backup_data; | ||
| 595 | if (backup_bytes) | ||
| 596 | std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes); | ||
| 597 | |||
| 598 | for (u64 offset = coarse_start_offset; offset < end_offset; offset += fill_size) { | ||
| 599 | std::memcpy(&dst_buffer[offset], &fill_data[0], | ||
| 600 | std::min(fill_size, end_offset - offset)); | ||
| 601 | } | ||
| 602 | |||
| 603 | if (backup_bytes) | ||
| 604 | std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes); | ||
| 605 | } else if (!is_tiled) { | ||
| 606 | ASSERT(type == SurfaceType::Color); | ||
| 607 | std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset], flush_end - flush_start); | ||
| 608 | } else { | ||
| 609 | gl_to_morton_fns[static_cast<size_t>(pixel_format)](stride, height, &gl_buffer[0], addr, | ||
| 610 | flush_start, flush_end); | ||
| 611 | } | ||
| 612 | } | ||
| 613 | |||
| 614 | MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192)); | ||
| 615 | void CachedSurface::UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, | ||
| 616 | GLuint draw_fb_handle) { | ||
| 617 | if (type == SurfaceType::Fill) | ||
| 618 | return; | ||
| 619 | |||
| 620 | MICROPROFILE_SCOPE(OpenGL_TextureUL); | ||
| 621 | |||
| 622 | ASSERT(gl_buffer_size == width * height * GetGLBytesPerPixel(pixel_format)); | ||
| 623 | |||
| 624 | // Load data from memory to the surface | ||
| 625 | GLint x0 = static_cast<GLint>(rect.left); | ||
| 626 | GLint y0 = static_cast<GLint>(rect.bottom); | ||
| 627 | size_t buffer_offset = (y0 * stride + x0) * GetGLBytesPerPixel(pixel_format); | ||
| 628 | |||
| 629 | const FormatTuple& tuple = GetFormatTuple(pixel_format); | ||
| 630 | GLuint target_tex = texture.handle; | ||
| 631 | |||
| 632 | // If not 1x scale, create 1x texture that we will blit from to replace texture subrect in | ||
| 633 | // surface | ||
| 634 | OGLTexture unscaled_tex; | ||
| 635 | if (res_scale != 1) { | ||
| 636 | x0 = 0; | ||
| 637 | y0 = 0; | ||
| 638 | |||
| 639 | unscaled_tex.Create(); | ||
| 640 | AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); | ||
| 641 | target_tex = unscaled_tex.handle; | ||
| 642 | } | ||
| 643 | |||
| 644 | OpenGLState cur_state = OpenGLState::GetCurState(); | ||
| 645 | |||
| 646 | GLuint old_tex = cur_state.texture_units[0].texture_2d; | ||
| 647 | cur_state.texture_units[0].texture_2d = target_tex; | ||
| 648 | cur_state.Apply(); | ||
| 649 | |||
| 650 | // Ensure no bad interactions with GL_UNPACK_ALIGNMENT | ||
| 651 | ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); | ||
| 652 | glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride)); | ||
| 653 | |||
| 654 | glActiveTexture(GL_TEXTURE0); | ||
| 655 | glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()), | ||
| 656 | static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type, | ||
| 657 | &gl_buffer[buffer_offset]); | ||
| 658 | |||
| 659 | glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | ||
| 660 | |||
| 661 | cur_state.texture_units[0].texture_2d = old_tex; | ||
| 662 | cur_state.Apply(); | ||
| 663 | |||
| 664 | if (res_scale != 1) { | ||
| 665 | auto scaled_rect = rect; | ||
| 666 | scaled_rect.left *= res_scale; | ||
| 667 | scaled_rect.top *= res_scale; | ||
| 668 | scaled_rect.right *= res_scale; | ||
| 669 | scaled_rect.bottom *= res_scale; | ||
| 670 | |||
| 671 | BlitTextures(unscaled_tex.handle, {0, rect.GetHeight(), rect.GetWidth(), 0}, texture.handle, | ||
| 672 | scaled_rect, type, read_fb_handle, draw_fb_handle); | ||
| 673 | } | ||
| 674 | } | ||
| 675 | |||
| 676 | MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64)); | ||
| 677 | void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, | ||
| 678 | GLuint draw_fb_handle) { | ||
| 679 | if (type == SurfaceType::Fill) | ||
| 680 | return; | ||
| 681 | |||
| 682 | MICROPROFILE_SCOPE(OpenGL_TextureDL); | ||
| 683 | |||
| 684 | if (gl_buffer == nullptr) { | ||
| 685 | gl_buffer_size = width * height * GetGLBytesPerPixel(pixel_format); | ||
| 686 | gl_buffer.reset(new u8[gl_buffer_size]); | ||
| 687 | } | ||
| 688 | |||
| 689 | OpenGLState state = OpenGLState::GetCurState(); | ||
| 690 | OpenGLState prev_state = state; | ||
| 691 | SCOPE_EXIT({ prev_state.Apply(); }); | ||
| 692 | |||
| 693 | const FormatTuple& tuple = GetFormatTuple(pixel_format); | ||
| 694 | |||
| 695 | // Ensure no bad interactions with GL_PACK_ALIGNMENT | ||
| 696 | ASSERT(stride * GetGLBytesPerPixel(pixel_format) % 4 == 0); | ||
| 697 | glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride)); | ||
| 698 | size_t buffer_offset = (rect.bottom * stride + rect.left) * GetGLBytesPerPixel(pixel_format); | ||
| 699 | |||
| 700 | // If not 1x scale, blit scaled texture to a new 1x texture and use that to flush | ||
| 701 | if (res_scale != 1) { | ||
| 702 | auto scaled_rect = rect; | ||
| 703 | scaled_rect.left *= res_scale; | ||
| 704 | scaled_rect.top *= res_scale; | ||
| 705 | scaled_rect.right *= res_scale; | ||
| 706 | scaled_rect.bottom *= res_scale; | ||
| 707 | |||
| 708 | OGLTexture unscaled_tex; | ||
| 709 | unscaled_tex.Create(); | ||
| 710 | |||
| 711 | MathUtil::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0}; | ||
| 712 | AllocateSurfaceTexture(unscaled_tex.handle, tuple, rect.GetWidth(), rect.GetHeight()); | ||
| 713 | BlitTextures(texture.handle, scaled_rect, unscaled_tex.handle, unscaled_tex_rect, type, | ||
| 714 | read_fb_handle, draw_fb_handle); | ||
| 715 | |||
| 716 | state.texture_units[0].texture_2d = unscaled_tex.handle; | ||
| 717 | state.Apply(); | ||
| 718 | |||
| 719 | glActiveTexture(GL_TEXTURE0); | ||
| 720 | glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]); | ||
| 721 | } else { | ||
| 722 | state.ResetTexture(texture.handle); | ||
| 723 | state.draw.read_framebuffer = read_fb_handle; | ||
| 724 | state.Apply(); | ||
| 725 | |||
| 726 | if (type == SurfaceType::Color || type == SurfaceType::Texture) { | ||
| 727 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, | ||
| 728 | texture.handle, 0); | ||
| 729 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||
| 730 | 0, 0); | ||
| 731 | } else if (type == SurfaceType::Depth) { | ||
| 732 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 733 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, | ||
| 734 | texture.handle, 0); | ||
| 735 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||
| 736 | } else { | ||
| 737 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 738 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, | ||
| 739 | texture.handle, 0); | ||
| 740 | } | ||
| 741 | glReadPixels(static_cast<GLint>(rect.left), static_cast<GLint>(rect.bottom), | ||
| 742 | static_cast<GLsizei>(rect.GetWidth()), static_cast<GLsizei>(rect.GetHeight()), | ||
| 743 | tuple.format, tuple.type, &gl_buffer[buffer_offset]); | ||
| 744 | } | ||
| 745 | |||
| 746 | glPixelStorei(GL_PACK_ROW_LENGTH, 0); | ||
| 747 | } | ||
| 748 | |||
| 749 | enum MatchFlags { | ||
| 750 | Invalid = 1, // Flag that can be applied to other match types, invalid matches require | ||
| 751 | // validation before they can be used | ||
| 752 | Exact = 1 << 1, // Surfaces perfectly match | ||
| 753 | SubRect = 1 << 2, // Surface encompasses params | ||
| 754 | Copy = 1 << 3, // Surface we can copy from | ||
| 755 | Expand = 1 << 4, // Surface that can expand params | ||
| 756 | TexCopy = 1 << 5 // Surface that will match a display transfer "texture copy" parameters | ||
| 757 | }; | ||
| 758 | |||
| 759 | constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) { | ||
| 760 | return static_cast<MatchFlags>(static_cast<int>(lhs) | static_cast<int>(rhs)); | ||
| 761 | } | ||
| 762 | |||
| 763 | /// Get the best surface match (and its match type) for the given flags | ||
| 764 | template <MatchFlags find_flags> | ||
| 765 | Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params, | ||
| 766 | ScaleMatch match_scale_type, | ||
| 767 | boost::optional<SurfaceInterval> validate_interval = boost::none) { | ||
| 768 | Surface match_surface = nullptr; | ||
| 769 | bool match_valid = false; | ||
| 770 | u32 match_scale = 0; | ||
| 771 | SurfaceInterval match_interval{}; | ||
| 772 | |||
| 773 | for (auto& pair : RangeFromInterval(surface_cache, params.GetInterval())) { | ||
| 774 | for (auto& surface : pair.second) { | ||
| 775 | bool res_scale_matched = match_scale_type == ScaleMatch::Exact | ||
| 776 | ? (params.res_scale == surface->res_scale) | ||
| 777 | : (params.res_scale <= surface->res_scale); | ||
| 778 | // validity will be checked in GetCopyableInterval | ||
| 779 | bool is_valid = | ||
| 780 | find_flags & MatchFlags::Copy | ||
| 781 | ? true | ||
| 782 | : surface->IsRegionValid(validate_interval.value_or(params.GetInterval())); | ||
| 783 | |||
| 784 | if (!(find_flags & MatchFlags::Invalid) && !is_valid) | ||
| 785 | continue; | ||
| 786 | |||
| 787 | auto IsMatch_Helper = [&](auto check_type, auto match_fn) { | ||
| 788 | if (!(find_flags & check_type)) | ||
| 789 | return; | ||
| 790 | |||
| 791 | bool matched; | ||
| 792 | SurfaceInterval surface_interval; | ||
| 793 | std::tie(matched, surface_interval) = match_fn(); | ||
| 794 | if (!matched) | ||
| 795 | return; | ||
| 796 | |||
| 797 | if (!res_scale_matched && match_scale_type != ScaleMatch::Ignore && | ||
| 798 | surface->type != SurfaceType::Fill) | ||
| 799 | return; | ||
| 800 | |||
| 801 | // Found a match, update only if this is better than the previous one | ||
| 802 | auto UpdateMatch = [&] { | ||
| 803 | match_surface = surface; | ||
| 804 | match_valid = is_valid; | ||
| 805 | match_scale = surface->res_scale; | ||
| 806 | match_interval = surface_interval; | ||
| 807 | }; | ||
| 808 | |||
| 809 | if (surface->res_scale > match_scale) { | ||
| 810 | UpdateMatch(); | ||
| 811 | return; | ||
| 812 | } else if (surface->res_scale < match_scale) { | ||
| 813 | return; | ||
| 814 | } | ||
| 815 | |||
| 816 | if (is_valid && !match_valid) { | ||
| 817 | UpdateMatch(); | ||
| 818 | return; | ||
| 819 | } else if (is_valid != match_valid) { | ||
| 820 | return; | ||
| 821 | } | ||
| 822 | |||
| 823 | if (boost::icl::length(surface_interval) > boost::icl::length(match_interval)) { | ||
| 824 | UpdateMatch(); | ||
| 825 | } | ||
| 826 | }; | ||
| 827 | IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Exact>{}, [&] { | ||
| 828 | return std::make_pair(surface->ExactMatch(params), surface->GetInterval()); | ||
| 829 | }); | ||
| 830 | IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::SubRect>{}, [&] { | ||
| 831 | return std::make_pair(surface->CanSubRect(params), surface->GetInterval()); | ||
| 832 | }); | ||
| 833 | IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Copy>{}, [&] { | ||
| 834 | auto copy_interval = | ||
| 835 | params.FromInterval(*validate_interval).GetCopyableInterval(surface); | ||
| 836 | bool matched = boost::icl::length(copy_interval & *validate_interval) != 0 && | ||
| 837 | surface->CanCopy(params, copy_interval); | ||
| 838 | return std::make_pair(matched, copy_interval); | ||
| 839 | }); | ||
| 840 | IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::Expand>{}, [&] { | ||
| 841 | return std::make_pair(surface->CanExpand(params), surface->GetInterval()); | ||
| 842 | }); | ||
| 843 | IsMatch_Helper(std::integral_constant<MatchFlags, MatchFlags::TexCopy>{}, [&] { | ||
| 844 | return std::make_pair(surface->CanTexCopy(params), surface->GetInterval()); | ||
| 845 | }); | ||
| 846 | } | ||
| 847 | } | ||
| 848 | return match_surface; | ||
| 849 | } | ||
| 850 | |||
| 851 | RasterizerCacheOpenGL::RasterizerCacheOpenGL() { | ||
| 852 | read_framebuffer.Create(); | ||
| 853 | draw_framebuffer.Create(); | ||
| 854 | |||
| 855 | attributeless_vao.Create(); | ||
| 856 | |||
| 857 | d24s8_abgr_buffer.Create(); | ||
| 858 | d24s8_abgr_buffer_size = 0; | ||
| 859 | |||
| 860 | const char* vs_source = R"( | ||
| 861 | #version 330 core | ||
| 862 | const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0)); | ||
| 863 | void main() { | ||
| 864 | gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0); | ||
| 865 | } | ||
| 866 | )"; | ||
| 867 | const char* fs_source = R"( | ||
| 868 | #version 330 core | ||
| 869 | |||
| 870 | uniform samplerBuffer tbo; | ||
| 871 | uniform vec2 tbo_size; | ||
| 872 | uniform vec4 viewport; | ||
| 873 | |||
| 874 | out vec4 color; | ||
| 875 | |||
| 876 | void main() { | ||
| 877 | vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw; | ||
| 878 | int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x); | ||
| 879 | color = texelFetch(tbo, tbo_offset).rabg; | ||
| 880 | } | ||
| 881 | )"; | ||
| 882 | d24s8_abgr_shader.Create(vs_source, nullptr, fs_source); | ||
| 883 | |||
| 884 | OpenGLState state = OpenGLState::GetCurState(); | ||
| 885 | GLuint old_program = state.draw.shader_program; | ||
| 886 | state.draw.shader_program = d24s8_abgr_shader.handle; | ||
| 887 | state.Apply(); | ||
| 888 | |||
| 889 | GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo"); | ||
| 890 | ASSERT(tbo_u_id != -1); | ||
| 891 | glUniform1i(tbo_u_id, 0); | ||
| 892 | |||
| 893 | state.draw.shader_program = old_program; | ||
| 894 | state.Apply(); | ||
| 895 | |||
| 896 | d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size"); | ||
| 897 | ASSERT(d24s8_abgr_tbo_size_u_id != -1); | ||
| 898 | d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport"); | ||
| 899 | ASSERT(d24s8_abgr_viewport_u_id != -1); | ||
| 900 | } | ||
| 901 | |||
| 902 | RasterizerCacheOpenGL::~RasterizerCacheOpenGL() { | ||
| 903 | FlushAll(); | ||
| 904 | while (!surface_cache.empty()) | ||
| 905 | UnregisterSurface(*surface_cache.begin()->second.begin()); | ||
| 906 | } | ||
| 907 | |||
| 908 | bool RasterizerCacheOpenGL::BlitSurfaces(const Surface& src_surface, | ||
| 909 | const MathUtil::Rectangle<u32>& src_rect, | ||
| 910 | const Surface& dst_surface, | ||
| 911 | const MathUtil::Rectangle<u32>& dst_rect) { | ||
| 912 | if (!SurfaceParams::CheckFormatsBlittable(src_surface->pixel_format, dst_surface->pixel_format)) | ||
| 913 | return false; | ||
| 914 | |||
| 915 | return BlitTextures(src_surface->texture.handle, src_rect, dst_surface->texture.handle, | ||
| 916 | dst_rect, src_surface->type, read_framebuffer.handle, | ||
| 917 | draw_framebuffer.handle); | ||
| 918 | } | ||
| 919 | |||
| 920 | void RasterizerCacheOpenGL::ConvertD24S8toABGR(GLuint src_tex, | ||
| 921 | const MathUtil::Rectangle<u32>& src_rect, | ||
| 922 | GLuint dst_tex, | ||
| 923 | const MathUtil::Rectangle<u32>& dst_rect) { | ||
| 924 | OpenGLState prev_state = OpenGLState::GetCurState(); | ||
| 925 | SCOPE_EXIT({ prev_state.Apply(); }); | ||
| 926 | |||
| 927 | OpenGLState state; | ||
| 928 | state.draw.read_framebuffer = read_framebuffer.handle; | ||
| 929 | state.draw.draw_framebuffer = draw_framebuffer.handle; | ||
| 930 | state.Apply(); | ||
| 931 | |||
| 932 | glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle); | ||
| 933 | |||
| 934 | GLsizeiptr target_pbo_size = src_rect.GetWidth() * src_rect.GetHeight() * 4; | ||
| 935 | if (target_pbo_size > d24s8_abgr_buffer_size) { | ||
| 936 | d24s8_abgr_buffer_size = target_pbo_size * 2; | ||
| 937 | glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY); | ||
| 938 | } | ||
| 939 | |||
| 940 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); | ||
| 941 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, src_tex, | ||
| 942 | 0); | ||
| 943 | glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom), | ||
| 944 | static_cast<GLsizei>(src_rect.GetWidth()), | ||
| 945 | static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, | ||
| 946 | 0); | ||
| 947 | |||
| 948 | glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); | ||
| 949 | |||
| 950 | // PBO now contains src_tex in RABG format | ||
| 951 | state.draw.shader_program = d24s8_abgr_shader.handle; | ||
| 952 | state.draw.vertex_array = attributeless_vao.handle; | ||
| 953 | state.viewport.x = static_cast<GLint>(dst_rect.left); | ||
| 954 | state.viewport.y = static_cast<GLint>(dst_rect.bottom); | ||
| 955 | state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth()); | ||
| 956 | state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight()); | ||
| 957 | state.Apply(); | ||
| 958 | |||
| 959 | OGLTexture tbo; | ||
| 960 | tbo.Create(); | ||
| 961 | glActiveTexture(GL_TEXTURE0); | ||
| 962 | glBindTexture(GL_TEXTURE_BUFFER, tbo.handle); | ||
| 963 | glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle); | ||
| 964 | |||
| 965 | glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()), | ||
| 966 | static_cast<GLfloat>(src_rect.GetHeight())); | ||
| 967 | glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x), | ||
| 968 | static_cast<GLfloat>(state.viewport.y), static_cast<GLfloat>(state.viewport.width), | ||
| 969 | static_cast<GLfloat>(state.viewport.height)); | ||
| 970 | |||
| 971 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0); | ||
| 972 | glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); | ||
| 973 | glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | ||
| 974 | |||
| 975 | glBindTexture(GL_TEXTURE_BUFFER, 0); | ||
| 976 | } | ||
| 977 | |||
| 978 | Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, | ||
| 979 | bool load_if_create) { | ||
| 980 | if (params.addr == 0 || params.height * params.width == 0) { | ||
| 981 | return nullptr; | ||
| 982 | } | ||
| 983 | // Use GetSurfaceSubRect instead | ||
| 984 | ASSERT(params.width == params.stride); | ||
| 985 | |||
| 986 | ASSERT(!params.is_tiled || (params.width % 8 == 0 && params.height % 8 == 0)); | ||
| 987 | |||
| 988 | // Check for an exact match in existing surfaces | ||
| 989 | Surface surface = | ||
| 990 | FindMatch<MatchFlags::Exact | MatchFlags::Invalid>(surface_cache, params, match_res_scale); | ||
| 991 | |||
| 992 | if (surface == nullptr) { | ||
| 993 | u16 target_res_scale = params.res_scale; | ||
| 994 | if (match_res_scale != ScaleMatch::Exact) { | ||
| 995 | // This surface may have a subrect of another surface with a higher res_scale, find it | ||
| 996 | // to adjust our params | ||
| 997 | SurfaceParams find_params = params; | ||
| 998 | Surface expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>( | ||
| 999 | surface_cache, find_params, match_res_scale); | ||
| 1000 | if (expandable != nullptr && expandable->res_scale > target_res_scale) { | ||
| 1001 | target_res_scale = expandable->res_scale; | ||
| 1002 | } | ||
| 1003 | // Keep res_scale when reinterpreting d24s8 -> rgba8 | ||
| 1004 | if (params.pixel_format == PixelFormat::RGBA8) { | ||
| 1005 | find_params.pixel_format = PixelFormat::D24S8; | ||
| 1006 | expandable = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>( | ||
| 1007 | surface_cache, find_params, match_res_scale); | ||
| 1008 | if (expandable != nullptr && expandable->res_scale > target_res_scale) { | ||
| 1009 | target_res_scale = expandable->res_scale; | ||
| 1010 | } | ||
| 1011 | } | ||
| 1012 | } | ||
| 1013 | SurfaceParams new_params = params; | ||
| 1014 | new_params.res_scale = target_res_scale; | ||
| 1015 | surface = CreateSurface(new_params); | ||
| 1016 | RegisterSurface(surface); | ||
| 1017 | } | ||
| 1018 | |||
| 1019 | if (load_if_create) { | ||
| 1020 | ValidateSurface(surface, params.addr, params.size); | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | return surface; | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams& params, | ||
| 1027 | ScaleMatch match_res_scale, | ||
| 1028 | bool load_if_create) { | ||
| 1029 | if (params.addr == 0 || params.height * params.width == 0) { | ||
| 1030 | return std::make_tuple(nullptr, MathUtil::Rectangle<u32>{}); | ||
| 1031 | } | ||
| 1032 | |||
| 1033 | // Attempt to find encompassing surface | ||
| 1034 | Surface surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, | ||
| 1035 | match_res_scale); | ||
| 1036 | |||
| 1037 | // Check if FindMatch failed because of res scaling | ||
| 1038 | // If that's the case create a new surface with | ||
| 1039 | // the dimensions of the lower res_scale surface | ||
| 1040 | // to suggest it should not be used again | ||
| 1041 | if (surface == nullptr && match_res_scale != ScaleMatch::Ignore) { | ||
| 1042 | surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>(surface_cache, params, | ||
| 1043 | ScaleMatch::Ignore); | ||
| 1044 | if (surface != nullptr) { | ||
| 1045 | ASSERT(surface->res_scale < params.res_scale); | ||
| 1046 | SurfaceParams new_params = *surface; | ||
| 1047 | new_params.res_scale = params.res_scale; | ||
| 1048 | |||
| 1049 | surface = CreateSurface(new_params); | ||
| 1050 | RegisterSurface(surface); | ||
| 1051 | } | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | SurfaceParams aligned_params = params; | ||
| 1055 | if (params.is_tiled) { | ||
| 1056 | aligned_params.height = Common::AlignUp(params.height, 8); | ||
| 1057 | aligned_params.width = Common::AlignUp(params.width, 8); | ||
| 1058 | aligned_params.stride = Common::AlignUp(params.stride, 8); | ||
| 1059 | aligned_params.UpdateParams(); | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | // Check for a surface we can expand before creating a new one | ||
| 1063 | if (surface == nullptr) { | ||
| 1064 | surface = FindMatch<MatchFlags::Expand | MatchFlags::Invalid>(surface_cache, aligned_params, | ||
| 1065 | match_res_scale); | ||
| 1066 | if (surface != nullptr) { | ||
| 1067 | aligned_params.width = aligned_params.stride; | ||
| 1068 | aligned_params.UpdateParams(); | ||
| 1069 | |||
| 1070 | SurfaceParams new_params = *surface; | ||
| 1071 | new_params.addr = std::min(aligned_params.addr, surface->addr); | ||
| 1072 | new_params.end = std::max(aligned_params.end, surface->end); | ||
| 1073 | new_params.size = new_params.end - new_params.addr; | ||
| 1074 | new_params.height = static_cast<u32>( | ||
| 1075 | new_params.size / aligned_params.BytesInPixels(aligned_params.stride)); | ||
| 1076 | ASSERT(new_params.size % aligned_params.BytesInPixels(aligned_params.stride) == 0); | ||
| 1077 | |||
| 1078 | Surface new_surface = CreateSurface(new_params); | ||
| 1079 | DuplicateSurface(surface, new_surface); | ||
| 1080 | |||
| 1081 | // Delete the expanded surface, this can't be done safely yet | ||
| 1082 | // because it may still be in use | ||
| 1083 | remove_surfaces.emplace(surface); | ||
| 1084 | |||
| 1085 | surface = new_surface; | ||
| 1086 | RegisterSurface(new_surface); | ||
| 1087 | } | ||
| 1088 | } | ||
| 1089 | |||
| 1090 | // No subrect found - create and return a new surface | ||
| 1091 | if (surface == nullptr) { | ||
| 1092 | SurfaceParams new_params = aligned_params; | ||
| 1093 | // Can't have gaps in a surface | ||
| 1094 | new_params.width = aligned_params.stride; | ||
| 1095 | new_params.UpdateParams(); | ||
| 1096 | // GetSurface will create the new surface and possibly adjust res_scale if necessary | ||
| 1097 | surface = GetSurface(new_params, match_res_scale, load_if_create); | ||
| 1098 | } else if (load_if_create) { | ||
| 1099 | ValidateSurface(surface, aligned_params.addr, aligned_params.size); | ||
| 1100 | } | ||
| 1101 | |||
| 1102 | return std::make_tuple(surface, surface->GetScaledSubRect(params)); | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | Surface RasterizerCacheOpenGL::GetTextureSurface(const void* config) { | ||
| 1106 | UNIMPLEMENTED(); | ||
| 1107 | return {}; | ||
| 1108 | } | ||
| 1109 | |||
| 1110 | SurfaceSurfaceRect_Tuple RasterizerCacheOpenGL::GetFramebufferSurfaces( | ||
| 1111 | bool using_color_fb, bool using_depth_fb, const MathUtil::Rectangle<s32>& viewport_rect) { | ||
| 1112 | UNIMPLEMENTED(); | ||
| 1113 | return {}; | ||
| 1114 | } | ||
| 1115 | |||
| 1116 | Surface RasterizerCacheOpenGL::GetFillSurface(const void* config) { | ||
| 1117 | UNIMPLEMENTED(); | ||
| 1118 | return {}; | ||
| 1119 | } | ||
| 1120 | |||
| 1121 | SurfaceRect_Tuple RasterizerCacheOpenGL::GetTexCopySurface(const SurfaceParams& params) { | ||
| 1122 | MathUtil::Rectangle<u32> rect{}; | ||
| 1123 | |||
| 1124 | Surface match_surface = FindMatch<MatchFlags::TexCopy | MatchFlags::Invalid>( | ||
| 1125 | surface_cache, params, ScaleMatch::Ignore); | ||
| 1126 | |||
| 1127 | if (match_surface != nullptr) { | ||
| 1128 | ValidateSurface(match_surface, params.addr, params.size); | ||
| 1129 | |||
| 1130 | SurfaceParams match_subrect; | ||
| 1131 | if (params.width != params.stride) { | ||
| 1132 | const u32 tiled_size = match_surface->is_tiled ? 8 : 1; | ||
| 1133 | match_subrect = params; | ||
| 1134 | match_subrect.width = | ||
| 1135 | static_cast<u32>(match_surface->PixelsInBytes(params.width) / tiled_size); | ||
| 1136 | match_subrect.stride = | ||
| 1137 | static_cast<u32>(match_surface->PixelsInBytes(params.stride) / tiled_size); | ||
| 1138 | match_subrect.height *= tiled_size; | ||
| 1139 | } else { | ||
| 1140 | match_subrect = match_surface->FromInterval(params.GetInterval()); | ||
| 1141 | ASSERT(match_subrect.GetInterval() == params.GetInterval()); | ||
| 1142 | } | ||
| 1143 | |||
| 1144 | rect = match_surface->GetScaledSubRect(match_subrect); | ||
| 1145 | } | ||
| 1146 | |||
| 1147 | return std::make_tuple(match_surface, rect); | ||
| 1148 | } | ||
| 1149 | |||
| 1150 | void RasterizerCacheOpenGL::DuplicateSurface(const Surface& src_surface, | ||
| 1151 | const Surface& dest_surface) { | ||
| 1152 | ASSERT(dest_surface->addr <= src_surface->addr && dest_surface->end >= src_surface->end); | ||
| 1153 | |||
| 1154 | BlitSurfaces(src_surface, src_surface->GetScaledRect(), dest_surface, | ||
| 1155 | dest_surface->GetScaledSubRect(*src_surface)); | ||
| 1156 | |||
| 1157 | dest_surface->invalid_regions -= src_surface->GetInterval(); | ||
| 1158 | dest_surface->invalid_regions += src_surface->invalid_regions; | ||
| 1159 | |||
| 1160 | SurfaceRegions regions; | ||
| 1161 | for (auto& pair : RangeFromInterval(dirty_regions, src_surface->GetInterval())) { | ||
| 1162 | if (pair.second == src_surface) { | ||
| 1163 | regions += pair.first; | ||
| 1164 | } | ||
| 1165 | } | ||
| 1166 | for (auto& interval : regions) { | ||
| 1167 | dirty_regions.set({interval, dest_surface}); | ||
| 1168 | } | ||
| 1169 | } | ||
| 1170 | |||
| 1171 | void RasterizerCacheOpenGL::ValidateSurface(const Surface& surface, PAddr addr, u64 size) { | ||
| 1172 | if (size == 0) | ||
| 1173 | return; | ||
| 1174 | |||
| 1175 | const SurfaceInterval validate_interval(addr, addr + size); | ||
| 1176 | |||
| 1177 | if (surface->type == SurfaceType::Fill) { | ||
| 1178 | // Sanity check, fill surfaces will always be valid when used | ||
| 1179 | ASSERT(surface->IsRegionValid(validate_interval)); | ||
| 1180 | return; | ||
| 1181 | } | ||
| 1182 | |||
| 1183 | while (true) { | ||
| 1184 | const auto it = surface->invalid_regions.find(validate_interval); | ||
| 1185 | if (it == surface->invalid_regions.end()) | ||
| 1186 | break; | ||
| 1187 | |||
| 1188 | const auto interval = *it & validate_interval; | ||
| 1189 | // Look for a valid surface to copy from | ||
| 1190 | SurfaceParams params = surface->FromInterval(interval); | ||
| 1191 | |||
| 1192 | Surface copy_surface = | ||
| 1193 | FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); | ||
| 1194 | if (copy_surface != nullptr) { | ||
| 1195 | SurfaceInterval copy_interval = params.GetCopyableInterval(copy_surface); | ||
| 1196 | CopySurface(copy_surface, surface, copy_interval); | ||
| 1197 | surface->invalid_regions.erase(copy_interval); | ||
| 1198 | continue; | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | // D24S8 to RGBA8 | ||
| 1202 | if (surface->pixel_format == PixelFormat::RGBA8) { | ||
| 1203 | params.pixel_format = PixelFormat::D24S8; | ||
| 1204 | Surface reinterpret_surface = | ||
| 1205 | FindMatch<MatchFlags::Copy>(surface_cache, params, ScaleMatch::Ignore, interval); | ||
| 1206 | if (reinterpret_surface != nullptr) { | ||
| 1207 | ASSERT(reinterpret_surface->pixel_format == PixelFormat::D24S8); | ||
| 1208 | |||
| 1209 | SurfaceInterval convert_interval = params.GetCopyableInterval(reinterpret_surface); | ||
| 1210 | SurfaceParams convert_params = surface->FromInterval(convert_interval); | ||
| 1211 | auto src_rect = reinterpret_surface->GetScaledSubRect(convert_params); | ||
| 1212 | auto dest_rect = surface->GetScaledSubRect(convert_params); | ||
| 1213 | |||
| 1214 | ConvertD24S8toABGR(reinterpret_surface->texture.handle, src_rect, | ||
| 1215 | surface->texture.handle, dest_rect); | ||
| 1216 | |||
| 1217 | surface->invalid_regions.erase(convert_interval); | ||
| 1218 | continue; | ||
| 1219 | } | ||
| 1220 | } | ||
| 1221 | |||
| 1222 | // Load data from 3DS memory | ||
| 1223 | FlushRegion(params.addr, params.size); | ||
| 1224 | surface->LoadGLBuffer(params.addr, params.end); | ||
| 1225 | surface->UploadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, | ||
| 1226 | draw_framebuffer.handle); | ||
| 1227 | surface->invalid_regions.erase(params.GetInterval()); | ||
| 1228 | } | ||
| 1229 | } | ||
| 1230 | |||
| 1231 | void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u64 size, Surface flush_surface) { | ||
| 1232 | if (size == 0) | ||
| 1233 | return; | ||
| 1234 | |||
| 1235 | const SurfaceInterval flush_interval(addr, addr + size); | ||
| 1236 | SurfaceRegions flushed_intervals; | ||
| 1237 | |||
| 1238 | for (auto& pair : RangeFromInterval(dirty_regions, flush_interval)) { | ||
| 1239 | // small sizes imply that this most likely comes from the cpu, flush the entire region | ||
| 1240 | // the point is to avoid thousands of small writes every frame if the cpu decides to access | ||
| 1241 | // that region, anything higher than 8 you're guaranteed it comes from a service | ||
| 1242 | const auto interval = size <= 8 ? pair.first : pair.first & flush_interval; | ||
| 1243 | auto& surface = pair.second; | ||
| 1244 | |||
| 1245 | if (flush_surface != nullptr && surface != flush_surface) | ||
| 1246 | continue; | ||
| 1247 | |||
| 1248 | // Sanity check, this surface is the last one that marked this region dirty | ||
| 1249 | ASSERT(surface->IsRegionValid(interval)); | ||
| 1250 | |||
| 1251 | if (surface->type != SurfaceType::Fill) { | ||
| 1252 | SurfaceParams params = surface->FromInterval(interval); | ||
| 1253 | surface->DownloadGLTexture(surface->GetSubRect(params), read_framebuffer.handle, | ||
| 1254 | draw_framebuffer.handle); | ||
| 1255 | } | ||
| 1256 | surface->FlushGLBuffer(boost::icl::first(interval), boost::icl::last_next(interval)); | ||
| 1257 | flushed_intervals += interval; | ||
| 1258 | } | ||
| 1259 | // Reset dirty regions | ||
| 1260 | dirty_regions -= flushed_intervals; | ||
| 1261 | } | ||
| 1262 | |||
| 1263 | void RasterizerCacheOpenGL::FlushAll() { | ||
| 1264 | FlushRegion(0, 0xFFFFFFFF); | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u64 size, const Surface& region_owner) { | ||
| 1268 | if (size == 0) | ||
| 1269 | return; | ||
| 1270 | |||
| 1271 | const SurfaceInterval invalid_interval(addr, addr + size); | ||
| 1272 | |||
| 1273 | if (region_owner != nullptr) { | ||
| 1274 | ASSERT(region_owner->type != SurfaceType::Texture); | ||
| 1275 | ASSERT(addr >= region_owner->addr && addr + size <= region_owner->end); | ||
| 1276 | // Surfaces can't have a gap | ||
| 1277 | ASSERT(region_owner->width == region_owner->stride); | ||
| 1278 | region_owner->invalid_regions.erase(invalid_interval); | ||
| 1279 | } | ||
| 1280 | |||
| 1281 | for (auto& pair : RangeFromInterval(surface_cache, invalid_interval)) { | ||
| 1282 | for (auto& cached_surface : pair.second) { | ||
| 1283 | if (cached_surface == region_owner) | ||
| 1284 | continue; | ||
| 1285 | |||
| 1286 | // If cpu is invalidating this region we want to remove it | ||
| 1287 | // to (likely) mark the memory pages as uncached | ||
| 1288 | if (region_owner == nullptr && size <= 8) { | ||
| 1289 | FlushRegion(cached_surface->addr, cached_surface->size, cached_surface); | ||
| 1290 | remove_surfaces.emplace(cached_surface); | ||
| 1291 | continue; | ||
| 1292 | } | ||
| 1293 | |||
| 1294 | const auto interval = cached_surface->GetInterval() & invalid_interval; | ||
| 1295 | cached_surface->invalid_regions.insert(interval); | ||
| 1296 | |||
| 1297 | // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures | ||
| 1298 | if (cached_surface->type == SurfaceType::Fill && | ||
| 1299 | cached_surface->IsSurfaceFullyInvalid()) { | ||
| 1300 | remove_surfaces.emplace(cached_surface); | ||
| 1301 | } | ||
| 1302 | } | ||
| 1303 | } | ||
| 1304 | |||
| 1305 | if (region_owner != nullptr) | ||
| 1306 | dirty_regions.set({invalid_interval, region_owner}); | ||
| 1307 | else | ||
| 1308 | dirty_regions.erase(invalid_interval); | ||
| 1309 | |||
| 1310 | for (auto& remove_surface : remove_surfaces) { | ||
| 1311 | if (remove_surface == region_owner) { | ||
| 1312 | Surface expanded_surface = FindMatch<MatchFlags::SubRect | MatchFlags::Invalid>( | ||
| 1313 | surface_cache, *region_owner, ScaleMatch::Ignore); | ||
| 1314 | ASSERT(expanded_surface); | ||
| 1315 | |||
| 1316 | if ((region_owner->invalid_regions - expanded_surface->invalid_regions).empty()) { | ||
| 1317 | DuplicateSurface(region_owner, expanded_surface); | ||
| 1318 | } else { | ||
| 1319 | continue; | ||
| 1320 | } | ||
| 1321 | } | ||
| 1322 | UnregisterSurface(remove_surface); | ||
| 1323 | } | ||
| 1324 | |||
| 1325 | remove_surfaces.clear(); | ||
| 1326 | } | ||
| 1327 | |||
| 1328 | Surface RasterizerCacheOpenGL::CreateSurface(const SurfaceParams& params) { | ||
| 1329 | Surface surface = std::make_shared<CachedSurface>(); | ||
| 1330 | static_cast<SurfaceParams&>(*surface) = params; | ||
| 1331 | |||
| 1332 | surface->texture.Create(); | ||
| 1333 | |||
| 1334 | surface->gl_buffer_size = 0; | ||
| 1335 | surface->invalid_regions.insert(surface->GetInterval()); | ||
| 1336 | AllocateSurfaceTexture(surface->texture.handle, GetFormatTuple(surface->pixel_format), | ||
| 1337 | surface->GetScaledWidth(), surface->GetScaledHeight()); | ||
| 1338 | |||
| 1339 | return surface; | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | void RasterizerCacheOpenGL::RegisterSurface(const Surface& surface) { | ||
| 1343 | if (surface->registered) { | ||
| 1344 | return; | ||
| 1345 | } | ||
| 1346 | surface->registered = true; | ||
| 1347 | surface_cache.add({surface->GetInterval(), SurfaceSet{surface}}); | ||
| 1348 | UpdatePagesCachedCount(surface->addr, surface->size, 1); | ||
| 1349 | } | ||
| 1350 | |||
| 1351 | void RasterizerCacheOpenGL::UnregisterSurface(const Surface& surface) { | ||
| 1352 | if (!surface->registered) { | ||
| 1353 | return; | ||
| 1354 | } | ||
| 1355 | surface->registered = false; | ||
| 1356 | UpdatePagesCachedCount(surface->addr, surface->size, -1); | ||
| 1357 | surface_cache.subtract({surface->GetInterval(), SurfaceSet{surface}}); | ||
| 1358 | } | ||
| 1359 | |||
| 1360 | void RasterizerCacheOpenGL::UpdatePagesCachedCount(PAddr addr, u64 size, int delta) { | ||
| 1361 | UNIMPLEMENTED(); | ||
| 1362 | } | ||
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h new file mode 100644 index 000000000..17ce0fee7 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h | |||
| @@ -0,0 +1,350 @@ | |||
| 1 | // Copyright 2015 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <array> | ||
| 8 | #include <memory> | ||
| 9 | #include <set> | ||
| 10 | #include <tuple> | ||
| 11 | #ifdef __GNUC__ | ||
| 12 | #pragma GCC diagnostic push | ||
| 13 | #pragma GCC diagnostic ignored "-Wunused-local-typedefs" | ||
| 14 | #endif | ||
| 15 | #include <boost/icl/interval_map.hpp> | ||
| 16 | #include <boost/icl/interval_set.hpp> | ||
| 17 | #ifdef __GNUC__ | ||
| 18 | #pragma GCC diagnostic pop | ||
| 19 | #endif | ||
| 20 | #include <glad/glad.h> | ||
| 21 | #include "common/assert.h" | ||
| 22 | #include "common/common_funcs.h" | ||
| 23 | #include "common/common_types.h" | ||
| 24 | #include "common/math_util.h" | ||
| 25 | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||
| 26 | |||
| 27 | struct CachedSurface; | ||
| 28 | using Surface = std::shared_ptr<CachedSurface>; | ||
| 29 | using SurfaceSet = std::set<Surface>; | ||
| 30 | |||
| 31 | using SurfaceRegions = boost::icl::interval_set<PAddr>; | ||
| 32 | using SurfaceMap = boost::icl::interval_map<PAddr, Surface>; | ||
| 33 | using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet>; | ||
| 34 | |||
| 35 | using SurfaceInterval = SurfaceCache::interval_type; | ||
| 36 | static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() && | ||
| 37 | std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(), | ||
| 38 | "incorrect interval types"); | ||
| 39 | |||
| 40 | using SurfaceRect_Tuple = std::tuple<Surface, MathUtil::Rectangle<u32>>; | ||
| 41 | using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>; | ||
| 42 | |||
| 43 | using PageMap = boost::icl::interval_map<u32, int>; | ||
| 44 | |||
| 45 | enum class ScaleMatch { | ||
| 46 | Exact, // only accept same res scale | ||
| 47 | Upscale, // only allow higher scale than params | ||
| 48 | Ignore // accept every scaled res | ||
| 49 | }; | ||
| 50 | |||
| 51 | struct SurfaceParams { | ||
| 52 | enum class PixelFormat { | ||
| 53 | // First 5 formats are shared between textures and color buffers | ||
| 54 | RGBA8 = 0, | ||
| 55 | RGB8 = 1, | ||
| 56 | RGB5A1 = 2, | ||
| 57 | RGB565 = 3, | ||
| 58 | RGBA4 = 4, | ||
| 59 | |||
| 60 | // Texture-only formats | ||
| 61 | IA8 = 5, | ||
| 62 | RG8 = 6, | ||
| 63 | I8 = 7, | ||
| 64 | A8 = 8, | ||
| 65 | IA4 = 9, | ||
| 66 | I4 = 10, | ||
| 67 | A4 = 11, | ||
| 68 | ETC1 = 12, | ||
| 69 | ETC1A4 = 13, | ||
| 70 | |||
| 71 | // Depth buffer-only formats | ||
| 72 | D16 = 14, | ||
| 73 | // gap | ||
| 74 | D24 = 16, | ||
| 75 | D24S8 = 17, | ||
| 76 | |||
| 77 | Invalid = 255, | ||
| 78 | }; | ||
| 79 | |||
| 80 | enum class SurfaceType { | ||
| 81 | Color = 0, | ||
| 82 | Texture = 1, | ||
| 83 | Depth = 2, | ||
| 84 | DepthStencil = 3, | ||
| 85 | Fill = 4, | ||
| 86 | Invalid = 5 | ||
| 87 | }; | ||
| 88 | |||
| 89 | static constexpr unsigned int GetFormatBpp(PixelFormat format) { | ||
| 90 | constexpr std::array<unsigned int, 18> bpp_table = { | ||
| 91 | 32, // RGBA8 | ||
| 92 | 24, // RGB8 | ||
| 93 | 16, // RGB5A1 | ||
| 94 | 16, // RGB565 | ||
| 95 | 16, // RGBA4 | ||
| 96 | 16, // IA8 | ||
| 97 | 16, // RG8 | ||
| 98 | 8, // I8 | ||
| 99 | 8, // A8 | ||
| 100 | 8, // IA4 | ||
| 101 | 4, // I4 | ||
| 102 | 4, // A4 | ||
| 103 | 4, // ETC1 | ||
| 104 | 8, // ETC1A4 | ||
| 105 | 16, // D16 | ||
| 106 | 0, | ||
| 107 | 24, // D24 | ||
| 108 | 32, // D24S8 | ||
| 109 | }; | ||
| 110 | |||
| 111 | assert(static_cast<size_t>(format) < bpp_table.size()); | ||
| 112 | return bpp_table[static_cast<size_t>(format)]; | ||
| 113 | } | ||
| 114 | unsigned int GetFormatBpp() const { | ||
| 115 | return GetFormatBpp(pixel_format); | ||
| 116 | } | ||
| 117 | |||
| 118 | static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) { | ||
| 119 | SurfaceType a_type = GetFormatType(pixel_format_a); | ||
| 120 | SurfaceType b_type = GetFormatType(pixel_format_b); | ||
| 121 | |||
| 122 | if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) && | ||
| 123 | (b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) { | ||
| 124 | return true; | ||
| 125 | } | ||
| 126 | |||
| 127 | if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) { | ||
| 128 | return true; | ||
| 129 | } | ||
| 130 | |||
| 131 | if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) { | ||
| 132 | return true; | ||
| 133 | } | ||
| 134 | |||
| 135 | return false; | ||
| 136 | } | ||
| 137 | |||
| 138 | static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) { | ||
| 139 | if ((unsigned int)pixel_format < 5) { | ||
| 140 | return SurfaceType::Color; | ||
| 141 | } | ||
| 142 | |||
| 143 | if ((unsigned int)pixel_format < 14) { | ||
| 144 | return SurfaceType::Texture; | ||
| 145 | } | ||
| 146 | |||
| 147 | if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) { | ||
| 148 | return SurfaceType::Depth; | ||
| 149 | } | ||
| 150 | |||
| 151 | if (pixel_format == PixelFormat::D24S8) { | ||
| 152 | return SurfaceType::DepthStencil; | ||
| 153 | } | ||
| 154 | |||
| 155 | return SurfaceType::Invalid; | ||
| 156 | } | ||
| 157 | |||
| 158 | /// Update the params "size", "end" and "type" from the already set "addr", "width", "height" | ||
| 159 | /// and "pixel_format" | ||
| 160 | void UpdateParams() { | ||
| 161 | if (stride == 0) { | ||
| 162 | stride = width; | ||
| 163 | } | ||
| 164 | type = GetFormatType(pixel_format); | ||
| 165 | size = !is_tiled ? BytesInPixels(stride * (height - 1) + width) | ||
| 166 | : BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8); | ||
| 167 | end = addr + size; | ||
| 168 | } | ||
| 169 | |||
| 170 | SurfaceInterval GetInterval() const { | ||
| 171 | return SurfaceInterval::right_open(addr, end); | ||
| 172 | } | ||
| 173 | |||
| 174 | // Returns the outer rectangle containing "interval" | ||
| 175 | SurfaceParams FromInterval(SurfaceInterval interval) const; | ||
| 176 | |||
| 177 | SurfaceInterval GetSubRectInterval(MathUtil::Rectangle<u32> unscaled_rect) const; | ||
| 178 | |||
| 179 | // Returns the region of the biggest valid rectange within interval | ||
| 180 | SurfaceInterval GetCopyableInterval(const Surface& src_surface) const; | ||
| 181 | |||
| 182 | u32 GetScaledWidth() const { | ||
| 183 | return width * res_scale; | ||
| 184 | } | ||
| 185 | |||
| 186 | u32 GetScaledHeight() const { | ||
| 187 | return height * res_scale; | ||
| 188 | } | ||
| 189 | |||
| 190 | MathUtil::Rectangle<u32> GetRect() const { | ||
| 191 | return {0, height, width, 0}; | ||
| 192 | } | ||
| 193 | |||
| 194 | MathUtil::Rectangle<u32> GetScaledRect() const { | ||
| 195 | return {0, GetScaledHeight(), GetScaledWidth(), 0}; | ||
| 196 | } | ||
| 197 | |||
| 198 | u64 PixelsInBytes(u64 size) const { | ||
| 199 | return size * CHAR_BIT / GetFormatBpp(pixel_format); | ||
| 200 | } | ||
| 201 | |||
| 202 | u64 BytesInPixels(u64 pixels) const { | ||
| 203 | return pixels * GetFormatBpp(pixel_format) / CHAR_BIT; | ||
| 204 | } | ||
| 205 | |||
| 206 | bool ExactMatch(const SurfaceParams& other_surface) const; | ||
| 207 | bool CanSubRect(const SurfaceParams& sub_surface) const; | ||
| 208 | bool CanExpand(const SurfaceParams& expanded_surface) const; | ||
| 209 | bool CanTexCopy(const SurfaceParams& texcopy_params) const; | ||
| 210 | |||
| 211 | MathUtil::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const; | ||
| 212 | MathUtil::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const; | ||
| 213 | |||
| 214 | PAddr addr = 0; | ||
| 215 | PAddr end = 0; | ||
| 216 | u64 size = 0; | ||
| 217 | |||
| 218 | u32 width = 0; | ||
| 219 | u32 height = 0; | ||
| 220 | u32 stride = 0; | ||
| 221 | u16 res_scale = 1; | ||
| 222 | |||
| 223 | bool is_tiled = false; | ||
| 224 | PixelFormat pixel_format = PixelFormat::Invalid; | ||
| 225 | SurfaceType type = SurfaceType::Invalid; | ||
| 226 | }; | ||
| 227 | |||
| 228 | struct CachedSurface : SurfaceParams { | ||
| 229 | bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const; | ||
| 230 | bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const; | ||
| 231 | |||
| 232 | bool IsRegionValid(SurfaceInterval interval) const { | ||
| 233 | return (invalid_regions.find(interval) == invalid_regions.end()); | ||
| 234 | } | ||
| 235 | |||
| 236 | bool IsSurfaceFullyInvalid() const { | ||
| 237 | return (invalid_regions & GetInterval()) == SurfaceRegions(GetInterval()); | ||
| 238 | } | ||
| 239 | |||
| 240 | bool registered = false; | ||
| 241 | SurfaceRegions invalid_regions; | ||
| 242 | |||
| 243 | u64 fill_size = 0; /// Number of bytes to read from fill_data | ||
| 244 | std::array<u8, 4> fill_data; | ||
| 245 | |||
| 246 | OGLTexture texture; | ||
| 247 | |||
| 248 | static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) { | ||
| 249 | // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type | ||
| 250 | return format == PixelFormat::Invalid | ||
| 251 | ? 0 | ||
| 252 | : (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture) | ||
| 253 | ? 4 | ||
| 254 | : SurfaceParams::GetFormatBpp(format) / 8; | ||
| 255 | } | ||
| 256 | |||
| 257 | std::unique_ptr<u8[]> gl_buffer; | ||
| 258 | size_t gl_buffer_size = 0; | ||
| 259 | |||
| 260 | // Read/Write data in 3DS memory to/from gl_buffer | ||
| 261 | void LoadGLBuffer(PAddr load_start, PAddr load_end); | ||
| 262 | void FlushGLBuffer(PAddr flush_start, PAddr flush_end); | ||
| 263 | |||
| 264 | // Upload/Download data in gl_buffer in/to this surface's texture | ||
| 265 | void UploadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, | ||
| 266 | GLuint draw_fb_handle); | ||
| 267 | void DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLuint read_fb_handle, | ||
| 268 | GLuint draw_fb_handle); | ||
| 269 | }; | ||
| 270 | |||
| 271 | class RasterizerCacheOpenGL : NonCopyable { | ||
| 272 | public: | ||
| 273 | RasterizerCacheOpenGL(); | ||
| 274 | ~RasterizerCacheOpenGL(); | ||
| 275 | |||
| 276 | /// Blit one surface's texture to another | ||
| 277 | bool BlitSurfaces(const Surface& src_surface, const MathUtil::Rectangle<u32>& src_rect, | ||
| 278 | const Surface& dst_surface, const MathUtil::Rectangle<u32>& dst_rect); | ||
| 279 | |||
| 280 | void ConvertD24S8toABGR(GLuint src_tex, const MathUtil::Rectangle<u32>& src_rect, | ||
| 281 | GLuint dst_tex, const MathUtil::Rectangle<u32>& dst_rect); | ||
| 282 | |||
| 283 | /// Copy one surface's region to another | ||
| 284 | void CopySurface(const Surface& src_surface, const Surface& dst_surface, | ||
| 285 | SurfaceInterval copy_interval); | ||
| 286 | |||
| 287 | /// Load a texture from 3DS memory to OpenGL and cache it (if not already cached) | ||
| 288 | Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale, | ||
| 289 | bool load_if_create); | ||
| 290 | |||
| 291 | /// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from | ||
| 292 | /// 3DS memory to OpenGL and caches it (if not already cached) | ||
| 293 | SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale, | ||
| 294 | bool load_if_create); | ||
| 295 | |||
| 296 | /// Get a surface based on the texture configuration | ||
| 297 | Surface GetTextureSurface(const void* config); | ||
| 298 | |||
| 299 | /// Get the color and depth surfaces based on the framebuffer configuration | ||
| 300 | SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb, | ||
| 301 | const MathUtil::Rectangle<s32>& viewport_rect); | ||
| 302 | |||
| 303 | /// Get a surface that matches the fill config | ||
| 304 | Surface GetFillSurface(const void* config); | ||
| 305 | |||
| 306 | /// Get a surface that matches a "texture copy" display transfer config | ||
| 307 | SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params); | ||
| 308 | |||
| 309 | /// Write any cached resources overlapping the region back to memory (if dirty) | ||
| 310 | void FlushRegion(PAddr addr, u64 size, Surface flush_surface = nullptr); | ||
| 311 | |||
| 312 | /// Mark region as being invalidated by region_owner (nullptr if 3DS memory) | ||
| 313 | void InvalidateRegion(PAddr addr, u64 size, const Surface& region_owner); | ||
| 314 | |||
| 315 | /// Flush all cached resources tracked by this cache manager | ||
| 316 | void FlushAll(); | ||
| 317 | |||
| 318 | private: | ||
| 319 | void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface); | ||
| 320 | |||
| 321 | /// Update surface's texture for given region when necessary | ||
| 322 | void ValidateSurface(const Surface& surface, PAddr addr, u64 size); | ||
| 323 | |||
| 324 | /// Create a new surface | ||
| 325 | Surface CreateSurface(const SurfaceParams& params); | ||
| 326 | |||
| 327 | /// Register surface into the cache | ||
| 328 | void RegisterSurface(const Surface& surface); | ||
| 329 | |||
| 330 | /// Remove surface from the cache | ||
| 331 | void UnregisterSurface(const Surface& surface); | ||
| 332 | |||
| 333 | /// Increase/decrease the number of surface in pages touching the specified region | ||
| 334 | void UpdatePagesCachedCount(PAddr addr, u64 size, int delta); | ||
| 335 | |||
| 336 | SurfaceCache surface_cache; | ||
| 337 | PageMap cached_pages; | ||
| 338 | SurfaceMap dirty_regions; | ||
| 339 | SurfaceSet remove_surfaces; | ||
| 340 | |||
| 341 | OGLFramebuffer read_framebuffer; | ||
| 342 | OGLFramebuffer draw_framebuffer; | ||
| 343 | |||
| 344 | OGLVertexArray attributeless_vao; | ||
| 345 | OGLBuffer d24s8_abgr_buffer; | ||
| 346 | GLsizeiptr d24s8_abgr_buffer_size; | ||
| 347 | OGLShader d24s8_abgr_shader; | ||
| 348 | GLint d24s8_abgr_tbo_size_u_id; | ||
| 349 | GLint d24s8_abgr_viewport_u_id; | ||
| 350 | }; | ||