2015-05-19 06:21:33 +02:00
|
|
|
// Copyright 2015 Citra Emulator Project
|
|
|
|
// Licensed under GPLv2 or any later version
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
2016-04-30 17:34:51 +02:00
|
|
|
#include <algorithm>
|
|
|
|
#include <atomic>
|
|
|
|
#include <cstring>
|
|
|
|
#include <iterator>
|
2016-04-17 00:57:57 +02:00
|
|
|
#include <unordered_set>
|
2016-04-30 17:34:51 +02:00
|
|
|
#include <utility>
|
|
|
|
#include <vector>
|
|
|
|
#include <glad/glad.h>
|
|
|
|
#include "common/bit_field.h"
|
|
|
|
#include "common/logging/log.h"
|
2015-05-19 06:21:33 +02:00
|
|
|
#include "common/math_util.h"
|
2015-08-17 23:25:21 +02:00
|
|
|
#include "common/microprofile.h"
|
2015-05-24 21:16:22 +02:00
|
|
|
#include "common/vector_math.h"
|
2016-12-23 14:37:40 +01:00
|
|
|
#include "core/frontend/emu_window.h"
|
2015-05-19 06:21:33 +02:00
|
|
|
#include "core/memory.h"
|
2017-01-28 11:33:35 +01:00
|
|
|
#include "core/settings.h"
|
2016-04-17 00:57:57 +02:00
|
|
|
#include "video_core/pica_state.h"
|
2016-09-21 08:52:38 +02:00
|
|
|
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
2016-04-30 17:34:51 +02:00
|
|
|
#include "video_core/renderer_opengl/gl_state.h"
|
2017-01-05 23:11:23 +01:00
|
|
|
#include "video_core/texture/texture_decode.h"
|
2016-04-17 00:57:57 +02:00
|
|
|
#include "video_core/utils.h"
|
|
|
|
#include "video_core/video_core.h"
|
|
|
|
|
|
|
|
struct FormatTuple {
|
|
|
|
GLint internal_format;
|
|
|
|
GLenum format;
|
|
|
|
GLenum type;
|
|
|
|
};
|
|
|
|
|
|
|
|
static const std::array<FormatTuple, 5> fb_format_tuples = {{
|
2016-09-18 02:38:01 +02:00
|
|
|
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
|
|
|
|
{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
|
|
|
|
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
|
|
|
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
|
|
|
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
2016-04-17 00:57:57 +02:00
|
|
|
}};
|
|
|
|
|
|
|
|
static const std::array<FormatTuple, 4> depth_format_tuples = {{
|
2016-09-18 02:38:01 +02:00
|
|
|
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
|
2016-04-17 00:57:57 +02:00
|
|
|
{},
|
2016-09-18 02:38:01 +02:00
|
|
|
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
|
|
|
|
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
|
2016-04-17 00:57:57 +02:00
|
|
|
}};
|
|
|
|
|
|
|
|
RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
|
|
|
|
transfer_framebuffers[0].Create();
|
|
|
|
transfer_framebuffers[1].Create();
|
|
|
|
}
|
2015-05-19 06:21:33 +02:00
|
|
|
|
|
|
|
RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
|
2016-04-17 00:57:57 +02:00
|
|
|
FlushAll();
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
static void MortonCopyPixels(CachedSurface::PixelFormat pixel_format, u32 width, u32 height,
|
|
|
|
u32 bytes_per_pixel, u32 gl_bytes_per_pixel, u8* morton_data,
|
|
|
|
u8* gl_data, bool morton_to_gl) {
|
2016-04-17 00:57:57 +02:00
|
|
|
using PixelFormat = CachedSurface::PixelFormat;
|
|
|
|
|
|
|
|
u8* data_ptrs[2];
|
|
|
|
u32 depth_stencil_shifts[2] = {24, 8};
|
2015-08-17 23:25:21 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
if (morton_to_gl) {
|
|
|
|
std::swap(depth_stencil_shifts[0], depth_stencil_shifts[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pixel_format == PixelFormat::D24S8) {
|
|
|
|
for (unsigned y = 0; y < height; ++y) {
|
|
|
|
for (unsigned x = 0; x < width; ++x) {
|
|
|
|
const u32 coarse_y = y & ~7;
|
2016-09-18 02:38:01 +02:00
|
|
|
u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) +
|
|
|
|
coarse_y * width * bytes_per_pixel;
|
2016-04-17 00:57:57 +02:00
|
|
|
u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
|
|
|
|
|
|
|
|
data_ptrs[morton_to_gl] = morton_data + morton_offset;
|
|
|
|
data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
// Swap depth and stencil value ordering since 3DS does not match OpenGL
|
|
|
|
u32 depth_stencil;
|
|
|
|
memcpy(&depth_stencil, data_ptrs[1], sizeof(u32));
|
2016-09-18 02:38:01 +02:00
|
|
|
depth_stencil = (depth_stencil << depth_stencil_shifts[0]) |
|
|
|
|
(depth_stencil >> depth_stencil_shifts[1]);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
memcpy(data_ptrs[0], &depth_stencil, sizeof(u32));
|
|
|
|
}
|
|
|
|
}
|
2015-05-19 06:21:33 +02:00
|
|
|
} else {
|
2016-04-17 00:57:57 +02:00
|
|
|
for (unsigned y = 0; y < height; ++y) {
|
|
|
|
for (unsigned x = 0; x < width; ++x) {
|
|
|
|
const u32 coarse_y = y & ~7;
|
2016-09-18 02:38:01 +02:00
|
|
|
u32 morton_offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) +
|
|
|
|
coarse_y * width * bytes_per_pixel;
|
2016-04-17 00:57:57 +02:00
|
|
|
u32 gl_pixel_index = (x + (height - 1 - y) * width) * gl_bytes_per_pixel;
|
|
|
|
|
|
|
|
data_ptrs[morton_to_gl] = morton_data + morton_offset;
|
|
|
|
data_ptrs[!morton_to_gl] = &gl_data[gl_pixel_index];
|
|
|
|
|
|
|
|
memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-06 23:23:21 +01:00
|
|
|
void RasterizerCacheOpenGL::BlitTextures(GLuint src_tex, GLuint dst_tex,
|
2016-09-18 02:38:01 +02:00
|
|
|
CachedSurface::SurfaceType type,
|
|
|
|
const MathUtil::Rectangle<int>& src_rect,
|
|
|
|
const MathUtil::Rectangle<int>& dst_rect) {
|
2016-04-17 00:57:57 +02:00
|
|
|
using SurfaceType = CachedSurface::SurfaceType;
|
|
|
|
|
|
|
|
OpenGLState cur_state = OpenGLState::GetCurState();
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
// Make sure textures aren't bound to texture units, since going to bind them to framebuffer
|
|
|
|
// components
|
2016-04-17 00:57:57 +02:00
|
|
|
OpenGLState::ResetTexture(src_tex);
|
|
|
|
OpenGLState::ResetTexture(dst_tex);
|
|
|
|
|
|
|
|
// Keep track of previous framebuffer bindings
|
2016-09-18 02:38:01 +02:00
|
|
|
GLuint old_fbs[2] = {cur_state.draw.read_framebuffer, cur_state.draw.draw_framebuffer};
|
2016-04-17 00:57:57 +02:00
|
|
|
cur_state.draw.read_framebuffer = transfer_framebuffers[0].handle;
|
|
|
|
cur_state.draw.draw_framebuffer = transfer_framebuffers[1].handle;
|
|
|
|
cur_state.Apply();
|
|
|
|
|
|
|
|
u32 buffers = 0;
|
|
|
|
|
|
|
|
if (type == SurfaceType::Color || type == SurfaceType::Texture) {
|
2016-09-18 02:38:01 +02:00
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, src_tex,
|
|
|
|
0);
|
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
|
|
|
0);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex,
|
|
|
|
0);
|
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
|
|
|
0);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
buffers = GL_COLOR_BUFFER_BIT;
|
|
|
|
} else if (type == SurfaceType::Depth) {
|
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, src_tex, 0);
|
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
|
|
|
|
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, dst_tex, 0);
|
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
|
|
|
|
|
|
|
buffers = GL_DEPTH_BUFFER_BIT;
|
|
|
|
} else if (type == SurfaceType::DepthStencil) {
|
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
2016-09-18 02:38:01 +02:00
|
|
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
|
|
|
src_tex, 0);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
2016-09-18 02:38:01 +02:00
|
|
|
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
|
|
|
dst_tex, 0);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
buffers = GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
|
|
|
}
|
|
|
|
|
2016-12-06 23:23:21 +01:00
|
|
|
glBlitFramebuffer(src_rect.left, src_rect.top, src_rect.right, src_rect.bottom, dst_rect.left,
|
|
|
|
dst_rect.top, dst_rect.right, dst_rect.bottom, buffers,
|
|
|
|
buffers == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
// Restore previous framebuffer bindings
|
|
|
|
cur_state.draw.read_framebuffer = old_fbs[0];
|
|
|
|
cur_state.draw.draw_framebuffer = old_fbs[1];
|
|
|
|
cur_state.Apply();
|
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
bool RasterizerCacheOpenGL::TryBlitSurfaces(CachedSurface* src_surface,
|
|
|
|
const MathUtil::Rectangle<int>& src_rect,
|
|
|
|
CachedSurface* dst_surface,
|
|
|
|
const MathUtil::Rectangle<int>& dst_rect) {
|
2016-04-17 00:57:57 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
if (!CachedSurface::CheckFormatsBlittable(src_surface->pixel_format,
|
|
|
|
dst_surface->pixel_format)) {
|
2016-04-17 00:57:57 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-06 23:23:21 +01:00
|
|
|
BlitTextures(src_surface->texture.handle, dst_surface->texture.handle,
|
|
|
|
CachedSurface::GetFormatType(src_surface->pixel_format), src_rect, dst_rect);
|
|
|
|
return true;
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
static void AllocateSurfaceTexture(GLuint texture, CachedSurface::PixelFormat pixel_format,
|
|
|
|
u32 width, u32 height) {
|
2016-04-17 00:57:57 +02:00
|
|
|
// Allocate an uninitialized texture of appropriate size and format for the surface
|
|
|
|
using SurfaceType = CachedSurface::SurfaceType;
|
|
|
|
|
|
|
|
OpenGLState cur_state = OpenGLState::GetCurState();
|
|
|
|
|
|
|
|
// Keep track of previous texture bindings
|
|
|
|
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
|
|
|
cur_state.texture_units[0].texture_2d = texture;
|
|
|
|
cur_state.Apply();
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
|
|
|
SurfaceType type = CachedSurface::GetFormatType(pixel_format);
|
|
|
|
|
|
|
|
FormatTuple tuple;
|
|
|
|
if (type == SurfaceType::Color) {
|
|
|
|
ASSERT((size_t)pixel_format < fb_format_tuples.size());
|
|
|
|
tuple = fb_format_tuples[(unsigned int)pixel_format];
|
|
|
|
} else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
|
|
|
|
size_t tuple_idx = (size_t)pixel_format - 14;
|
|
|
|
ASSERT(tuple_idx < depth_format_tuples.size());
|
|
|
|
tuple = depth_format_tuples[tuple_idx];
|
|
|
|
} else {
|
2016-09-18 02:38:01 +02:00
|
|
|
tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, width, height, 0, tuple.format,
|
|
|
|
tuple.type, nullptr);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
|
|
|
|
// Restore previous texture bindings
|
|
|
|
cur_state.texture_units[0].texture_2d = old_tex;
|
|
|
|
cur_state.Apply();
|
|
|
|
}
|
|
|
|
|
|
|
|
MICROPROFILE_DEFINE(OpenGL_SurfaceUpload, "OpenGL", "Surface Upload", MP_RGB(128, 64, 192));
|
2016-09-18 02:38:01 +02:00
|
|
|
CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bool match_res_scale,
|
|
|
|
bool load_if_create) {
|
2016-04-17 00:57:57 +02:00
|
|
|
using PixelFormat = CachedSurface::PixelFormat;
|
|
|
|
using SurfaceType = CachedSurface::SurfaceType;
|
|
|
|
|
|
|
|
if (params.addr == 0) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
u32 params_size =
|
|
|
|
params.width * params.height * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
// Check for an exact match in existing surfaces
|
|
|
|
CachedSurface* best_exact_surface = nullptr;
|
|
|
|
float exact_surface_goodness = -1.f;
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
auto surface_interval =
|
|
|
|
boost::icl::interval<PAddr>::right_open(params.addr, params.addr + params_size);
|
2016-04-17 00:57:57 +02:00
|
|
|
auto range = surface_cache.equal_range(surface_interval);
|
|
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
|
|
for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
|
|
|
|
CachedSurface* surface = it2->get();
|
|
|
|
|
|
|
|
// Check if the request matches the surface exactly
|
2016-09-18 02:38:01 +02:00
|
|
|
if (params.addr == surface->addr && params.width == surface->width &&
|
|
|
|
params.height == surface->height && params.pixel_format == surface->pixel_format) {
|
2016-04-17 00:57:57 +02:00
|
|
|
// Make sure optional param-matching criteria are fulfilled
|
|
|
|
bool tiling_match = (params.is_tiled == surface->is_tiled);
|
2016-09-18 02:38:01 +02:00
|
|
|
bool res_scale_match = (params.res_scale_width == surface->res_scale_width &&
|
|
|
|
params.res_scale_height == surface->res_scale_height);
|
2016-04-17 00:57:57 +02:00
|
|
|
if (!match_res_scale || res_scale_match) {
|
|
|
|
// Prioritize same-tiling and highest resolution surfaces
|
2016-09-18 02:38:01 +02:00
|
|
|
float match_goodness =
|
|
|
|
(float)tiling_match + surface->res_scale_width * surface->res_scale_height;
|
2016-04-17 00:57:57 +02:00
|
|
|
if (match_goodness > exact_surface_goodness || surface->dirty) {
|
|
|
|
exact_surface_goodness = match_goodness;
|
|
|
|
best_exact_surface = surface;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the best exact surface if found
|
|
|
|
if (best_exact_surface != nullptr) {
|
|
|
|
return best_exact_surface;
|
|
|
|
}
|
|
|
|
|
|
|
|
// No matching surfaces found, so create a new one
|
|
|
|
u8* texture_src_data = Memory::GetPhysicalPointer(params.addr);
|
|
|
|
if (texture_src_data == nullptr) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
MICROPROFILE_SCOPE(OpenGL_SurfaceUpload);
|
|
|
|
|
2016-12-04 14:16:56 +01:00
|
|
|
// Stride only applies to linear images.
|
|
|
|
ASSERT(params.pixel_stride == 0 || !params.is_tiled);
|
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
std::shared_ptr<CachedSurface> new_surface = std::make_shared<CachedSurface>();
|
|
|
|
|
|
|
|
new_surface->addr = params.addr;
|
|
|
|
new_surface->size = params_size;
|
|
|
|
|
|
|
|
new_surface->texture.Create();
|
|
|
|
new_surface->width = params.width;
|
|
|
|
new_surface->height = params.height;
|
2016-12-04 14:16:56 +01:00
|
|
|
new_surface->pixel_stride = params.pixel_stride;
|
2016-04-17 00:57:57 +02:00
|
|
|
new_surface->res_scale_width = params.res_scale_width;
|
|
|
|
new_surface->res_scale_height = params.res_scale_height;
|
|
|
|
|
|
|
|
new_surface->is_tiled = params.is_tiled;
|
|
|
|
new_surface->pixel_format = params.pixel_format;
|
|
|
|
new_surface->dirty = false;
|
|
|
|
|
|
|
|
if (!load_if_create) {
|
|
|
|
// Don't load any data; just allocate the surface's texture
|
2016-09-18 02:38:01 +02:00
|
|
|
AllocateSurfaceTexture(new_surface->texture.handle, new_surface->pixel_format,
|
|
|
|
new_surface->GetScaledWidth(), new_surface->GetScaledHeight());
|
2016-04-17 00:57:57 +02:00
|
|
|
} else {
|
2016-09-18 02:38:01 +02:00
|
|
|
// TODO: Consider attempting subrect match in existing surfaces and direct blit here instead
|
|
|
|
// of memory upload below if that's a common scenario in some game
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
Memory::RasterizerFlushRegion(params.addr, params_size);
|
|
|
|
|
|
|
|
// Load data from memory to the new surface
|
|
|
|
OpenGLState cur_state = OpenGLState::GetCurState();
|
|
|
|
|
|
|
|
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
|
|
|
cur_state.texture_units[0].texture_2d = new_surface->texture.handle;
|
|
|
|
cur_state.Apply();
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
|
|
|
if (!new_surface->is_tiled) {
|
|
|
|
// TODO: Ensure this will always be a color format, not a depth or other format
|
|
|
|
ASSERT((size_t)new_surface->pixel_format < fb_format_tuples.size());
|
|
|
|
const FormatTuple& tuple = fb_format_tuples[(unsigned int)params.pixel_format];
|
|
|
|
|
2016-12-04 14:16:56 +01:00
|
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)new_surface->pixel_stride);
|
2016-04-17 00:57:57 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height, 0,
|
|
|
|
tuple.format, tuple.type, texture_src_data);
|
2016-12-04 14:16:56 +01:00
|
|
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
2016-04-17 00:57:57 +02:00
|
|
|
} else {
|
|
|
|
SurfaceType type = CachedSurface::GetFormatType(new_surface->pixel_format);
|
|
|
|
if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) {
|
|
|
|
FormatTuple tuple;
|
|
|
|
if ((size_t)params.pixel_format < fb_format_tuples.size()) {
|
|
|
|
tuple = fb_format_tuples[(unsigned int)params.pixel_format];
|
|
|
|
} else {
|
|
|
|
// Texture
|
2016-09-18 02:38:01 +02:00
|
|
|
tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<Math::Vec4<u8>> tex_buffer(params.width * params.height);
|
|
|
|
|
2017-01-05 23:11:23 +01:00
|
|
|
Pica::Texture::TextureInfo tex_info;
|
2016-04-17 00:57:57 +02:00
|
|
|
tex_info.width = params.width;
|
|
|
|
tex_info.height = params.height;
|
2017-01-28 05:51:59 +01:00
|
|
|
tex_info.format = (Pica::TexturingRegs::TextureFormat)params.pixel_format;
|
2017-01-06 04:19:06 +01:00
|
|
|
tex_info.SetDefaultStride();
|
2016-04-17 00:57:57 +02:00
|
|
|
tex_info.physical_address = params.addr;
|
|
|
|
|
|
|
|
for (unsigned y = 0; y < params.height; ++y) {
|
|
|
|
for (unsigned x = 0; x < params.width; ++x) {
|
2017-01-05 23:11:23 +01:00
|
|
|
tex_buffer[x + params.width * y] = Pica::Texture::LookupTexture(
|
2016-09-18 02:38:01 +02:00
|
|
|
texture_src_data, x, params.height - 1 - y, tex_info);
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height,
|
|
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, tex_buffer.data());
|
2016-04-17 00:57:57 +02:00
|
|
|
} else {
|
2016-09-18 02:38:01 +02:00
|
|
|
// Depth/Stencil formats need special treatment since they aren't sampleable using
|
|
|
|
// LookupTexture and can't use RGBA format
|
2016-04-17 00:57:57 +02:00
|
|
|
size_t tuple_idx = (size_t)params.pixel_format - 14;
|
|
|
|
ASSERT(tuple_idx < depth_format_tuples.size());
|
|
|
|
const FormatTuple& tuple = depth_format_tuples[tuple_idx];
|
|
|
|
|
|
|
|
u32 bytes_per_pixel = CachedSurface::GetFormatBpp(params.pixel_format) / 8;
|
|
|
|
|
|
|
|
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
|
|
|
|
bool use_4bpp = (params.pixel_format == PixelFormat::D24);
|
|
|
|
|
|
|
|
u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel;
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
std::vector<u8> temp_fb_depth_buffer(params.width * params.height *
|
|
|
|
gl_bytes_per_pixel);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
u8* temp_fb_depth_buffer_ptr =
|
|
|
|
use_4bpp ? temp_fb_depth_buffer.data() + 1 : temp_fb_depth_buffer.data();
|
2016-04-17 00:57:57 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
MortonCopyPixels(params.pixel_format, params.width, params.height, bytes_per_pixel,
|
|
|
|
gl_bytes_per_pixel, texture_src_data, temp_fb_depth_buffer_ptr,
|
|
|
|
true);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
glTexImage2D(GL_TEXTURE_2D, 0, tuple.internal_format, params.width, params.height,
|
|
|
|
0, tuple.format, tuple.type, temp_fb_depth_buffer.data());
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
}
|
2015-08-17 23:25:21 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
// If not 1x scale, blit 1x texture to a new scaled texture and replace texture in surface
|
|
|
|
if (new_surface->res_scale_width != 1.f || new_surface->res_scale_height != 1.f) {
|
|
|
|
OGLTexture scaled_texture;
|
|
|
|
scaled_texture.Create();
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
AllocateSurfaceTexture(scaled_texture.handle, new_surface->pixel_format,
|
|
|
|
new_surface->GetScaledWidth(), new_surface->GetScaledHeight());
|
|
|
|
BlitTextures(new_surface->texture.handle, scaled_texture.handle,
|
|
|
|
CachedSurface::GetFormatType(new_surface->pixel_format),
|
|
|
|
MathUtil::Rectangle<int>(0, 0, new_surface->width, new_surface->height),
|
|
|
|
MathUtil::Rectangle<int>(0, 0, new_surface->GetScaledWidth(),
|
|
|
|
new_surface->GetScaledHeight()));
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
new_surface->texture.Release();
|
|
|
|
new_surface->texture.handle = scaled_texture.handle;
|
|
|
|
scaled_texture.handle = 0;
|
|
|
|
cur_state.texture_units[0].texture_2d = new_surface->texture.handle;
|
|
|
|
cur_state.Apply();
|
|
|
|
}
|
|
|
|
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
|
|
|
|
cur_state.texture_units[0].texture_2d = old_tex;
|
|
|
|
cur_state.Apply();
|
|
|
|
}
|
|
|
|
|
|
|
|
Memory::RasterizerMarkRegionCached(new_surface->addr, new_surface->size, 1);
|
2016-09-18 02:38:01 +02:00
|
|
|
surface_cache.add(std::make_pair(boost::icl::interval<PAddr>::right_open(
|
|
|
|
new_surface->addr, new_surface->addr + new_surface->size),
|
|
|
|
std::set<std::shared_ptr<CachedSurface>>({new_surface})));
|
2016-04-17 00:57:57 +02:00
|
|
|
return new_surface.get();
|
|
|
|
}
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params,
|
|
|
|
bool match_res_scale, bool load_if_create,
|
|
|
|
MathUtil::Rectangle<int>& out_rect) {
|
2016-04-17 00:57:57 +02:00
|
|
|
if (params.addr == 0) {
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 total_pixels = params.width * params.height;
|
|
|
|
u32 params_size = total_pixels * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
|
|
|
|
|
|
|
|
// Attempt to find encompassing surfaces
|
|
|
|
CachedSurface* best_subrect_surface = nullptr;
|
|
|
|
float subrect_surface_goodness = -1.f;
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
auto surface_interval =
|
|
|
|
boost::icl::interval<PAddr>::right_open(params.addr, params.addr + params_size);
|
2016-04-17 00:57:57 +02:00
|
|
|
auto cache_upper_bound = surface_cache.upper_bound(surface_interval);
|
|
|
|
for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) {
|
|
|
|
for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
|
|
|
|
CachedSurface* surface = it2->get();
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
// Check if the request is contained in the surface
|
|
|
|
if (params.addr >= surface->addr &&
|
|
|
|
params.addr + params_size - 1 <= surface->addr + surface->size - 1 &&
|
2016-09-18 02:38:01 +02:00
|
|
|
params.pixel_format == surface->pixel_format) {
|
2016-04-17 00:57:57 +02:00
|
|
|
// Make sure optional param-matching criteria are fulfilled
|
|
|
|
bool tiling_match = (params.is_tiled == surface->is_tiled);
|
2016-09-18 02:38:01 +02:00
|
|
|
bool res_scale_match = (params.res_scale_width == surface->res_scale_width &&
|
|
|
|
params.res_scale_height == surface->res_scale_height);
|
2016-04-17 00:57:57 +02:00
|
|
|
if (!match_res_scale || res_scale_match) {
|
|
|
|
// Prioritize same-tiling and highest resolution surfaces
|
2016-09-18 02:38:01 +02:00
|
|
|
float match_goodness =
|
|
|
|
(float)tiling_match + surface->res_scale_width * surface->res_scale_height;
|
2016-04-17 00:57:57 +02:00
|
|
|
if (match_goodness > subrect_surface_goodness || surface->dirty) {
|
|
|
|
subrect_surface_goodness = match_goodness;
|
|
|
|
best_subrect_surface = surface;
|
|
|
|
}
|
|
|
|
}
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
|
|
|
}
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return the best subrect surface if found
|
|
|
|
if (best_subrect_surface != nullptr) {
|
2016-09-18 02:38:01 +02:00
|
|
|
unsigned int bytes_per_pixel =
|
|
|
|
(CachedSurface::GetFormatBpp(best_subrect_surface->pixel_format) / 8);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
int x0, y0;
|
|
|
|
|
|
|
|
if (!params.is_tiled) {
|
|
|
|
u32 begin_pixel_index = (params.addr - best_subrect_surface->addr) / bytes_per_pixel;
|
|
|
|
x0 = begin_pixel_index % best_subrect_surface->width;
|
|
|
|
y0 = begin_pixel_index / best_subrect_surface->width;
|
|
|
|
|
|
|
|
out_rect = MathUtil::Rectangle<int>(x0, y0, x0 + params.width, y0 + params.height);
|
|
|
|
} else {
|
|
|
|
u32 bytes_per_tile = 8 * 8 * bytes_per_pixel;
|
|
|
|
u32 tiles_per_row = best_subrect_surface->width / 8;
|
|
|
|
|
|
|
|
u32 begin_tile_index = (params.addr - best_subrect_surface->addr) / bytes_per_tile;
|
|
|
|
x0 = begin_tile_index % tiles_per_row * 8;
|
|
|
|
y0 = begin_tile_index / tiles_per_row * 8;
|
|
|
|
|
|
|
|
// Tiled surfaces are flipped vertically in the rasterizer vs. 3DS memory.
|
2016-09-18 02:38:01 +02:00
|
|
|
out_rect =
|
|
|
|
MathUtil::Rectangle<int>(x0, best_subrect_surface->height - y0, x0 + params.width,
|
|
|
|
best_subrect_surface->height - (y0 + params.height));
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
out_rect.left = (int)(out_rect.left * best_subrect_surface->res_scale_width);
|
|
|
|
out_rect.right = (int)(out_rect.right * best_subrect_surface->res_scale_width);
|
|
|
|
out_rect.top = (int)(out_rect.top * best_subrect_surface->res_scale_height);
|
|
|
|
out_rect.bottom = (int)(out_rect.bottom * best_subrect_surface->res_scale_height);
|
2015-05-19 06:21:33 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
return best_subrect_surface;
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
// No subrect found - create and return a new surface
|
|
|
|
if (!params.is_tiled) {
|
2016-09-18 02:38:01 +02:00
|
|
|
out_rect = MathUtil::Rectangle<int>(0, 0, (int)(params.width * params.res_scale_width),
|
|
|
|
(int)(params.height * params.res_scale_height));
|
2016-04-17 00:57:57 +02:00
|
|
|
} else {
|
2016-09-18 02:38:01 +02:00
|
|
|
out_rect = MathUtil::Rectangle<int>(0, (int)(params.height * params.res_scale_height),
|
|
|
|
(int)(params.width * params.res_scale_width), 0);
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return GetSurface(params, match_res_scale, load_if_create);
|
|
|
|
}
|
|
|
|
|
2016-09-19 03:01:46 +02:00
|
|
|
CachedSurface* RasterizerCacheOpenGL::GetTextureSurface(
|
2017-01-28 05:51:59 +01:00
|
|
|
const Pica::TexturingRegs::FullTextureConfig& config) {
|
2017-01-05 23:11:23 +01:00
|
|
|
|
|
|
|
Pica::Texture::TextureInfo info =
|
|
|
|
Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
CachedSurface params;
|
|
|
|
params.addr = info.physical_address;
|
|
|
|
params.width = info.width;
|
|
|
|
params.height = info.height;
|
|
|
|
params.is_tiled = true;
|
|
|
|
params.pixel_format = CachedSurface::PixelFormatFromTextureFormat(info.format);
|
|
|
|
return GetSurface(params, false, true);
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
std::tuple<CachedSurface*, CachedSurface*, MathUtil::Rectangle<int>>
|
2017-01-28 06:47:34 +01:00
|
|
|
RasterizerCacheOpenGL::GetFramebufferSurfaces(
|
|
|
|
const Pica::FramebufferRegs::FramebufferConfig& config) {
|
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
const auto& regs = Pica::g_state.regs;
|
|
|
|
|
|
|
|
// Make sur that framebuffers don't overlap if both color and depth are being used
|
|
|
|
u32 fb_area = config.GetWidth() * config.GetHeight();
|
2016-09-18 02:38:01 +02:00
|
|
|
bool framebuffers_overlap =
|
|
|
|
config.GetColorBufferPhysicalAddress() != 0 &&
|
|
|
|
config.GetDepthBufferPhysicalAddress() != 0 &&
|
|
|
|
MathUtil::IntervalsIntersect(
|
|
|
|
config.GetColorBufferPhysicalAddress(),
|
|
|
|
fb_area * GPU::Regs::BytesPerPixel(GPU::Regs::PixelFormat(config.color_format.Value())),
|
|
|
|
config.GetDepthBufferPhysicalAddress(),
|
2017-01-28 06:47:34 +01:00
|
|
|
fb_area * Pica::FramebufferRegs::BytesPerDepthPixel(config.depth_format));
|
2016-04-17 00:57:57 +02:00
|
|
|
bool using_color_fb = config.GetColorBufferPhysicalAddress() != 0;
|
2017-01-28 06:47:34 +01:00
|
|
|
bool using_depth_fb =
|
|
|
|
config.GetDepthBufferPhysicalAddress() != 0 &&
|
|
|
|
(regs.framebuffer.output_merger.depth_test_enable ||
|
|
|
|
regs.framebuffer.output_merger.depth_write_enable || !framebuffers_overlap);
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
if (framebuffers_overlap && using_color_fb && using_depth_fb) {
|
2016-09-18 02:38:01 +02:00
|
|
|
LOG_CRITICAL(Render_OpenGL, "Color and depth framebuffer memory regions overlap; "
|
|
|
|
"overlapping framebuffers not supported!");
|
2016-04-17 00:57:57 +02:00
|
|
|
using_depth_fb = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get color and depth surfaces
|
|
|
|
CachedSurface color_params;
|
|
|
|
CachedSurface depth_params;
|
|
|
|
color_params.width = depth_params.width = config.GetWidth();
|
|
|
|
color_params.height = depth_params.height = config.GetHeight();
|
|
|
|
color_params.is_tiled = depth_params.is_tiled = true;
|
|
|
|
|
2016-12-30 05:28:27 +01:00
|
|
|
// Set the internal resolution, assume the same scaling factor for top and bottom screens
|
|
|
|
const Layout::FramebufferLayout& layout = VideoCore::g_emu_window->GetFramebufferLayout();
|
|
|
|
if (Settings::values.resolution_factor == 0.0f) {
|
|
|
|
// Auto - scale resolution to the window size
|
2016-09-18 02:38:01 +02:00
|
|
|
color_params.res_scale_width = depth_params.res_scale_width =
|
|
|
|
(float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth;
|
|
|
|
color_params.res_scale_height = depth_params.res_scale_height =
|
|
|
|
(float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight;
|
2016-12-30 05:28:27 +01:00
|
|
|
} else {
|
|
|
|
// Otherwise, scale the resolution by the specified factor
|
|
|
|
color_params.res_scale_width = Settings::values.resolution_factor;
|
|
|
|
depth_params.res_scale_width = Settings::values.resolution_factor;
|
|
|
|
color_params.res_scale_height = Settings::values.resolution_factor;
|
|
|
|
depth_params.res_scale_height = Settings::values.resolution_factor;
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
color_params.addr = config.GetColorBufferPhysicalAddress();
|
|
|
|
color_params.pixel_format = CachedSurface::PixelFormatFromColorFormat(config.color_format);
|
|
|
|
|
|
|
|
depth_params.addr = config.GetDepthBufferPhysicalAddress();
|
|
|
|
depth_params.pixel_format = CachedSurface::PixelFormatFromDepthFormat(config.depth_format);
|
|
|
|
|
|
|
|
MathUtil::Rectangle<int> color_rect;
|
2016-09-18 02:38:01 +02:00
|
|
|
CachedSurface* color_surface =
|
|
|
|
using_color_fb ? GetSurfaceRect(color_params, true, true, color_rect) : nullptr;
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
MathUtil::Rectangle<int> depth_rect;
|
2016-09-18 02:38:01 +02:00
|
|
|
CachedSurface* depth_surface =
|
|
|
|
using_depth_fb ? GetSurfaceRect(depth_params, true, true, depth_rect) : nullptr;
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
// Sanity check to make sure found surfaces aren't the same
|
|
|
|
if (using_depth_fb && using_color_fb && color_surface == depth_surface) {
|
2016-09-18 02:38:01 +02:00
|
|
|
LOG_CRITICAL(
|
|
|
|
Render_OpenGL,
|
|
|
|
"Color and depth framebuffer surfaces overlap; overlapping surfaces not supported!");
|
2016-04-17 00:57:57 +02:00
|
|
|
using_depth_fb = false;
|
|
|
|
depth_surface = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
MathUtil::Rectangle<int> rect;
|
2015-08-25 04:11:46 +02:00
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
if (color_surface != nullptr && depth_surface != nullptr &&
|
|
|
|
(depth_rect.left != color_rect.left || depth_rect.top != color_rect.top)) {
|
|
|
|
// Can't specify separate color and depth viewport offsets in OpenGL, so re-zero both if
|
|
|
|
// they don't match
|
2016-04-17 00:57:57 +02:00
|
|
|
if (color_rect.left != 0 || color_rect.top != 0) {
|
|
|
|
color_surface = GetSurface(color_params, true, true);
|
|
|
|
}
|
2015-08-25 04:11:46 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
if (depth_rect.left != 0 || depth_rect.top != 0) {
|
|
|
|
depth_surface = GetSurface(depth_params, true, true);
|
|
|
|
}
|
2015-08-25 04:11:46 +02:00
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
if (!color_surface->is_tiled) {
|
2016-09-18 02:38:01 +02:00
|
|
|
rect = MathUtil::Rectangle<int>(
|
|
|
|
0, 0, (int)(color_params.width * color_params.res_scale_width),
|
|
|
|
(int)(color_params.height * color_params.res_scale_height));
|
2015-05-19 06:21:33 +02:00
|
|
|
} else {
|
2016-09-18 02:38:01 +02:00
|
|
|
rect = MathUtil::Rectangle<int>(
|
|
|
|
0, (int)(color_params.height * color_params.res_scale_height),
|
|
|
|
(int)(color_params.width * color_params.res_scale_width), 0);
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
2016-04-17 00:57:57 +02:00
|
|
|
} else if (color_surface != nullptr) {
|
|
|
|
rect = color_rect;
|
|
|
|
} else if (depth_surface != nullptr) {
|
|
|
|
rect = depth_rect;
|
|
|
|
} else {
|
|
|
|
rect = MathUtil::Rectangle<int>(0, 0, 0, 0);
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
2016-04-17 00:57:57 +02:00
|
|
|
|
|
|
|
return std::make_tuple(color_surface, depth_surface, rect);
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|
|
|
|
|
2016-04-17 00:57:57 +02:00
|
|
|
CachedSurface* RasterizerCacheOpenGL::TryGetFillSurface(const GPU::Regs::MemoryFillConfig& config) {
|
2016-09-18 02:38:01 +02:00
|
|
|
auto surface_interval =
|
|
|
|
boost::icl::interval<PAddr>::right_open(config.GetStartAddress(), config.GetEndAddress());
|
2016-04-17 00:57:57 +02:00
|
|
|
auto range = surface_cache.equal_range(surface_interval);
|
|
|
|
for (auto it = range.first; it != range.second; ++it) {
|
|
|
|
for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) {
|
|
|
|
int bits_per_value = 0;
|
|
|
|
if (config.fill_24bit) {
|
|
|
|
bits_per_value = 24;
|
|
|
|
} else if (config.fill_32bit) {
|
|
|
|
bits_per_value = 32;
|
|
|
|
} else {
|
|
|
|
bits_per_value = 16;
|
|
|
|
}
|
|
|
|
|
|
|
|
CachedSurface* surface = it2->get();
|
|
|
|
|
|
|
|
if (surface->addr == config.GetStartAddress() &&
|
|
|
|
CachedSurface::GetFormatBpp(surface->pixel_format) == bits_per_value &&
|
2016-09-18 02:38:01 +02:00
|
|
|
(surface->width * surface->height *
|
|
|
|
CachedSurface::GetFormatBpp(surface->pixel_format) / 8) ==
|
|
|
|
(config.GetEndAddress() - config.GetStartAddress())) {
|
2016-04-17 00:57:57 +02:00
|
|
|
return surface;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
MICROPROFILE_DEFINE(OpenGL_SurfaceDownload, "OpenGL", "Surface Download", MP_RGB(128, 192, 64));
|
|
|
|
void RasterizerCacheOpenGL::FlushSurface(CachedSurface* surface) {
|
|
|
|
using PixelFormat = CachedSurface::PixelFormat;
|
|
|
|
using SurfaceType = CachedSurface::SurfaceType;
|
|
|
|
|
|
|
|
if (!surface->dirty) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MICROPROFILE_SCOPE(OpenGL_SurfaceDownload);
|
|
|
|
|
|
|
|
u8* dst_buffer = Memory::GetPhysicalPointer(surface->addr);
|
|
|
|
if (dst_buffer == nullptr) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
OpenGLState cur_state = OpenGLState::GetCurState();
|
|
|
|
GLuint old_tex = cur_state.texture_units[0].texture_2d;
|
|
|
|
|
|
|
|
OGLTexture unscaled_tex;
|
|
|
|
GLuint texture_to_flush = surface->texture.handle;
|
|
|
|
|
|
|
|
// If not 1x scale, blit scaled texture to a new 1x texture and use that to flush
|
|
|
|
if (surface->res_scale_width != 1.f || surface->res_scale_height != 1.f) {
|
|
|
|
unscaled_tex.Create();
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
AllocateSurfaceTexture(unscaled_tex.handle, surface->pixel_format, surface->width,
|
|
|
|
surface->height);
|
|
|
|
BlitTextures(
|
|
|
|
surface->texture.handle, unscaled_tex.handle,
|
|
|
|
CachedSurface::GetFormatType(surface->pixel_format),
|
2016-04-17 00:57:57 +02:00
|
|
|
MathUtil::Rectangle<int>(0, 0, surface->GetScaledWidth(), surface->GetScaledHeight()),
|
|
|
|
MathUtil::Rectangle<int>(0, 0, surface->width, surface->height));
|
|
|
|
|
|
|
|
texture_to_flush = unscaled_tex.handle;
|
|
|
|
}
|
|
|
|
|
|
|
|
cur_state.texture_units[0].texture_2d = texture_to_flush;
|
|
|
|
cur_state.Apply();
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
|
|
|
|
|
|
if (!surface->is_tiled) {
|
|
|
|
// TODO: Ensure this will always be a color format, not a depth or other format
|
|
|
|
ASSERT((size_t)surface->pixel_format < fb_format_tuples.size());
|
|
|
|
const FormatTuple& tuple = fb_format_tuples[(unsigned int)surface->pixel_format];
|
|
|
|
|
2016-12-04 14:16:56 +01:00
|
|
|
glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)surface->pixel_stride);
|
2016-04-17 00:57:57 +02:00
|
|
|
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, dst_buffer);
|
2016-12-04 14:16:56 +01:00
|
|
|
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
2016-04-17 00:57:57 +02:00
|
|
|
} else {
|
|
|
|
SurfaceType type = CachedSurface::GetFormatType(surface->pixel_format);
|
|
|
|
if (type != SurfaceType::Depth && type != SurfaceType::DepthStencil) {
|
|
|
|
ASSERT((size_t)surface->pixel_format < fb_format_tuples.size());
|
|
|
|
const FormatTuple& tuple = fb_format_tuples[(unsigned int)surface->pixel_format];
|
|
|
|
|
|
|
|
u32 bytes_per_pixel = CachedSurface::GetFormatBpp(surface->pixel_format) / 8;
|
|
|
|
|
|
|
|
std::vector<u8> temp_gl_buffer(surface->width * surface->height * bytes_per_pixel);
|
|
|
|
|
|
|
|
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, temp_gl_buffer.data());
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
// Directly copy pixels. Internal OpenGL color formats are consistent so no conversion
|
|
|
|
// is necessary.
|
|
|
|
MortonCopyPixels(surface->pixel_format, surface->width, surface->height,
|
|
|
|
bytes_per_pixel, bytes_per_pixel, dst_buffer, temp_gl_buffer.data(),
|
|
|
|
false);
|
2016-04-17 00:57:57 +02:00
|
|
|
} else {
|
2016-09-18 02:38:01 +02:00
|
|
|
// Depth/Stencil formats need special treatment since they aren't sampleable using
|
|
|
|
// LookupTexture and can't use RGBA format
|
2016-04-17 00:57:57 +02:00
|
|
|
size_t tuple_idx = (size_t)surface->pixel_format - 14;
|
|
|
|
ASSERT(tuple_idx < depth_format_tuples.size());
|
|
|
|
const FormatTuple& tuple = depth_format_tuples[tuple_idx];
|
|
|
|
|
|
|
|
u32 bytes_per_pixel = CachedSurface::GetFormatBpp(surface->pixel_format) / 8;
|
|
|
|
|
|
|
|
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
|
|
|
|
bool use_4bpp = (surface->pixel_format == PixelFormat::D24);
|
|
|
|
|
|
|
|
u32 gl_bytes_per_pixel = use_4bpp ? 4 : bytes_per_pixel;
|
|
|
|
|
|
|
|
std::vector<u8> temp_gl_buffer(surface->width * surface->height * gl_bytes_per_pixel);
|
|
|
|
|
|
|
|
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, temp_gl_buffer.data());
|
|
|
|
|
|
|
|
u8* temp_gl_buffer_ptr = use_4bpp ? temp_gl_buffer.data() + 1 : temp_gl_buffer.data();
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
MortonCopyPixels(surface->pixel_format, surface->width, surface->height,
|
|
|
|
bytes_per_pixel, gl_bytes_per_pixel, dst_buffer, temp_gl_buffer_ptr,
|
|
|
|
false);
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
surface->dirty = false;
|
|
|
|
|
|
|
|
cur_state.texture_units[0].texture_2d = old_tex;
|
|
|
|
cur_state.Apply();
|
|
|
|
}
|
|
|
|
|
2016-09-18 02:38:01 +02:00
|
|
|
void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, const CachedSurface* skip_surface,
|
|
|
|
bool invalidate) {
|
2016-04-17 00:57:57 +02:00
|
|
|
if (size == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gather up unique surfaces that touch the region
|
|
|
|
std::unordered_set<std::shared_ptr<CachedSurface>> touching_surfaces;
|
|
|
|
|
|
|
|
auto surface_interval = boost::icl::interval<PAddr>::right_open(addr, addr + size);
|
|
|
|
auto cache_upper_bound = surface_cache.upper_bound(surface_interval);
|
|
|
|
for (auto it = surface_cache.lower_bound(surface_interval); it != cache_upper_bound; ++it) {
|
2016-09-18 02:38:01 +02:00
|
|
|
std::copy_if(it->second.begin(), it->second.end(),
|
|
|
|
std::inserter(touching_surfaces, touching_surfaces.end()),
|
|
|
|
[skip_surface](std::shared_ptr<CachedSurface> surface) {
|
|
|
|
return (surface.get() != skip_surface);
|
|
|
|
});
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Flush and invalidate surfaces
|
|
|
|
for (auto surface : touching_surfaces) {
|
|
|
|
FlushSurface(surface.get());
|
|
|
|
if (invalidate) {
|
|
|
|
Memory::RasterizerMarkRegionCached(surface->addr, surface->size, -1);
|
2016-09-18 02:38:01 +02:00
|
|
|
surface_cache.subtract(
|
|
|
|
std::make_pair(boost::icl::interval<PAddr>::right_open(
|
|
|
|
surface->addr, surface->addr + surface->size),
|
|
|
|
std::set<std::shared_ptr<CachedSurface>>({surface})));
|
2016-04-17 00:57:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void RasterizerCacheOpenGL::FlushAll() {
|
|
|
|
for (auto& surfaces : surface_cache) {
|
|
|
|
for (auto& surface : surfaces.second) {
|
|
|
|
FlushSurface(surface.get());
|
|
|
|
}
|
|
|
|
}
|
2015-05-19 06:21:33 +02:00
|
|
|
}
|