Files
Vita3K/vita3k/renderer/src/gl/surface_cache.cpp
T
scribam df2ffeb48e external: Update submodules (#2926)
* external: Update CLI11 to version v2.3.2

* external: Update MoltenVK to 1.2.5

* external: Update SPIRV-Cross to sdk-1.3.261.1

* external: Update Vulkan-Headers to v1.3.261

* external: Update capstone to 5.0.1

* external: Update cubeb

* external: Update dirent to 1.24

* external: Update fmt to 10.1.1

* external: Update glad to 2.0.4

* external: Update glslang to sdk-1.3.261.1

* external: Update googletest to v1.14.0

* external: Update imgui to version v1.89.9

* external: Update imgui_club

* external: Update miniz to version 3.0.2

* external: Update nativefiledialog-extended to version v1.1.0

* external: Update openssl to 3.1.2

* external: Update pugixml to version v1.13

* external: Update spdlog to v1.12.0

* external: Update stb

* external: Update tracy to v0.9.1

* external: Update vita-toolchain

* external: Update xxHash to v0.8.2

* external: Update yaml-cpp to version 0.8.0
2023-09-16 13:42:44 +02:00

768 lines
36 KiB
C++

// Vita3K emulator project
// Copyright (C) 2023 Vita3K team
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#include <gxm/functions.h>
#include <renderer/gl/functions.h>
#include <renderer/gl/surface_cache.h>
#include <renderer/gl/types.h>
#include <util/log.h>
#include <chrono>
namespace renderer::gl {
static constexpr std::uint64_t CASTED_UNUSED_TEXTURE_PURGE_SECS = 40;
GLSurfaceCache::GLSurfaceCache() = default;
void GLSurfaceCache::do_typeless_copy(const GLuint dest_texture, const GLuint source_texture, const GLenum dest_internal,
const GLenum dest_upload_format, const GLenum dest_type, const GLenum source_format, const GLenum source_type, const int offset_x,
const int offset_y, const int width, const int height, const int dest_width, const int dest_height, const std::size_t total_source_size) {
static constexpr GLsizei I32_SIGNED_MAX = 0x7FFFFFFF;
if (!typeless_copy_buffer[0]) {
if (!typeless_copy_buffer.init(reinterpret_cast<renderer::Generator *>(glGenBuffers), reinterpret_cast<renderer::Deleter *>(glDeleteBuffers))) {
LOG_ERROR("Unable to initialize a typeless copy buffer");
return;
}
}
if (total_source_size > typeless_copy_buffer_size) {
glBindBuffer(GL_PIXEL_PACK_BUFFER, typeless_copy_buffer[0]);
glBufferData(GL_PIXEL_PACK_BUFFER, total_source_size, nullptr, GL_STATIC_COPY);
typeless_copy_buffer_size = total_source_size;
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, typeless_copy_buffer[0]);
glGetTextureSubImage(source_texture, 0, offset_x, offset_y, 0, width, height, 1, source_format, source_type, I32_SIGNED_MAX, nullptr);
glBindBuffer(GL_PIXEL_PACK_BUFFER, GL_NONE);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, typeless_copy_buffer[0]);
glBindTexture(GL_TEXTURE_2D, dest_texture);
glTexImage2D(GL_TEXTURE_2D, 0, dest_internal, dest_width, dest_height, 0, dest_upload_format, dest_type, nullptr);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, GL_NONE);
}
GLuint GLSurfaceCache::retrieve_color_surface_texture_handle(const State &state, std::uint16_t width, std::uint16_t height, const std::uint16_t pixel_stride,
const SceGxmColorBaseFormat base_format, Ptr<void> address, SurfaceTextureRetrievePurpose purpose, std::uint32_t &swizzle,
std::uint16_t *stored_height, std::uint16_t *stored_width) {
// Create the key to access the cache struct
const std::uint64_t key = address.address();
const uint32_t original_width = width;
const uint32_t original_height = height;
width *= state.res_multiplier;
height *= state.res_multiplier;
// Of course, this works under the assumption that range must be unique :D
auto ite = color_surface_textures.lower_bound(key);
bool invalidated = false;
bool overlap = false;
if (ite != color_surface_textures.end()) {
// if the surface found does not overlap the surface we want, it's useless to look at it
overlap = (ite->first + ite->second->total_bytes) > key;
}
if (!overlap && purpose != SurfaceTextureRetrievePurpose::WRITING) {
// not part of a surface, let the texture cache handle it
return 0;
}
GLenum surface_internal_format = color::translate_internal_format(base_format);
GLenum surface_upload_format = color::translate_format(base_format);
GLenum surface_data_type = color::translate_type(base_format);
std::size_t bytes_per_stride = pixel_stride * color::bytes_per_pixel(base_format);
std::size_t total_surface_size = bytes_per_stride * original_height;
if (overlap) {
GLColorSurfaceCacheInfo &info = *ite->second;
auto used_iterator = std::find(last_use_color_surface_index.begin(), last_use_color_surface_index.end(), ite->first);
if (stored_height) {
*stored_height = info.original_height;
}
if (stored_width) {
*stored_width = info.original_width;
}
if (purpose == SurfaceTextureRetrievePurpose::READING
&& (base_format == SCE_GXM_COLOR_BASE_FORMAT_U8U8U8 || info.format == SCE_GXM_COLOR_BASE_FORMAT_U8U8U8)
&& base_format != info.format)
// don't even try to match u8u8u8 with something else
return 0;
// There are four situations I think of:
// 1. Different base address, lookup for write, in this case, if the cached surface range contains the given address, then
// probably this cached surface has already been freed GPU-wise. So erase.
// 2. Same base address, but width and height change to be larger, or format change if write. Remake a new one for both read and write sitatation.
// 3. Out of cache range. In write case, create a new one, in read case, lul
// 4. Read situation with smaller width and height, probably need to extract the needed region out.
const bool addr_in_range_of_cache = ((key + total_surface_size) <= (ite->first + info.total_bytes));
const bool cache_probably_freed = ((ite->first != key) && addr_in_range_of_cache && (purpose == SurfaceTextureRetrievePurpose::WRITING));
const bool surface_extent_changed = (info.width < width) || (info.height < height);
bool surface_stat_changed = false;
if (ite->first == key) {
if (purpose == SurfaceTextureRetrievePurpose::WRITING) {
surface_stat_changed = surface_extent_changed || (base_format != info.format);
} else {
// If the extent changed but format is not the same, then the probability of it being a cast is high
surface_stat_changed = surface_extent_changed && (base_format == info.format);
}
}
if (cache_probably_freed) {
for (auto ite = framebuffer_array.begin(); ite != framebuffer_array.end();) {
if ((ite->first & 0xFFFFFFFF) == key) {
ite = framebuffer_array.erase(ite);
} else {
ite++;
}
}
// Clear out. We will recreate later
color_surface_textures.erase(ite);
invalidated = true;
} else if (surface_stat_changed) {
// Remake locally to avoid making changes to framebuffer array
uint16_t prev_width = info.width;
uint16_t prev_height = info.height;
info.width = width;
info.height = height;
info.original_width = original_width;
info.original_height = original_height;
info.pixel_stride = pixel_stride;
info.format = base_format;
info.total_bytes = total_surface_size;
info.flags = 0;
bool store_rawly = false;
auto remake_and_apply_filters = [&](GLuint bind_texture_id) {
if (prev_width <= info.width && prev_height <= info.height && surface_extent_changed) {
GLuint temp_texture;
glGenTextures(1, &temp_texture);
glBindTexture(GL_TEXTURE_2D, temp_texture);
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, prev_width, prev_height, 0, surface_upload_format, surface_data_type, nullptr);
if (!store_rawly) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
glCopyImageSubData(bind_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, temp_texture, GL_TEXTURE_2D, 0, 0, 0, 0, prev_width, prev_height, 1);
glBindTexture(GL_TEXTURE_2D, bind_texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, width, height, 0, surface_upload_format, surface_data_type, nullptr);
if (!store_rawly) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
glCopyImageSubData(temp_texture, GL_TEXTURE_2D, 0, 0, 0, 0, bind_texture_id, GL_TEXTURE_2D, 0, 0, 0, 0, prev_width, prev_height, 1);
glDeleteTextures(1, &temp_texture);
} else {
glBindTexture(GL_TEXTURE_2D, bind_texture_id);
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, width, height, 0, surface_upload_format, surface_data_type, nullptr);
if (!store_rawly) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
}
};
if (info.gl_expected_read_texture_view[0]) {
remake_and_apply_filters(info.gl_ping_pong_texture[0]);
}
if (state.features.preserve_f16_nan_as_u16 && color::is_write_surface_stored_rawly(base_format)) {
surface_internal_format = color::get_raw_store_internal_type(base_format);
surface_upload_format = color::get_raw_store_upload_format_type(base_format);
surface_data_type = color::get_raw_store_upload_data_type(base_format);
store_rawly = true;
}
// This handles some situation where game may stores texture in a larger texture then rebind it
remake_and_apply_filters(info.gl_texture[0]);
if (info.gl_ping_pong_texture[0]) {
remake_and_apply_filters(info.gl_ping_pong_texture[0]);
}
info.casted_textures.clear();
}
if ((purpose == SurfaceTextureRetrievePurpose::WRITING) && (swizzle != info.swizzle)) {
info.swizzle = swizzle;
} else if (purpose == SurfaceTextureRetrievePurpose::READING) {
swizzle = info.swizzle;
}
if (!addr_in_range_of_cache) {
if (purpose == SurfaceTextureRetrievePurpose::WRITING) {
invalidated = true;
}
} else if (purpose == SurfaceTextureRetrievePurpose::READING) {
// If we read and it's still in range
if (used_iterator != last_use_color_surface_index.end()) {
last_use_color_surface_index.erase(used_iterator);
}
last_use_color_surface_index.push_back(ite->first);
if (info.flags & GLSurfaceCacheInfo::FLAG_DIRTY) {
// We can't use this texture sadly :( If it uses for writing of course it will be gud gud
return 0;
}
bool castable = (info.pixel_stride == pixel_stride);
std::size_t bytes_per_pixel_requested = color::bytes_per_pixel(base_format);
std::size_t bytes_per_pixel_in_store = color::bytes_per_pixel(info.format);
// Check if castable. Technically the income format should be texture format, but this is for easier logic.
// When it's required. I may change :p
if (base_format != info.format) {
if (bytes_per_pixel_requested > bytes_per_pixel_in_store) {
castable = (((bytes_per_pixel_requested % bytes_per_pixel_in_store) == 0) && (info.pixel_stride % pixel_stride == 0) && ((info.pixel_stride / pixel_stride) == (bytes_per_pixel_requested / bytes_per_pixel_in_store)));
} else {
castable = (((bytes_per_pixel_in_store % bytes_per_pixel_requested) == 0) && (pixel_stride % info.pixel_stride == 0) && ((pixel_stride / info.pixel_stride) == (bytes_per_pixel_in_store / bytes_per_pixel_requested)));
}
if (castable) {
// Check if the GL implementation actually store raw like this (a safe check)
if ((bytes_per_pixel_requested != color::bytes_per_pixel_in_gl_storage(base_format)) || (bytes_per_pixel_in_store != color::bytes_per_pixel_in_gl_storage(info.format))) {
LOG_ERROR("One or both two surface formats requested=0x{:X} and inStore=0x{:X} does not support bit-casting. Please report to developers!",
fmt::underlying(base_format), fmt::underlying(info.format));
return 0;
}
} else {
static bool has_happened = false;
LOG_ERROR_IF(!has_happened, "Two surface formats requested=0x{:X} and inStore=0x{:X} are not castable!", fmt::underlying(base_format), fmt::underlying(info.format));
has_happened = true;
return 0;
}
}
if (castable) {
const std::size_t data_delta = address.address() - ite->first;
std::size_t start_sourced_line = (data_delta / bytes_per_stride) * state.res_multiplier;
std::size_t start_x = (data_delta % bytes_per_stride) / color::bytes_per_pixel(base_format) * state.res_multiplier;
if (static_cast<std::uint16_t>(start_sourced_line + height) > info.height) {
LOG_ERROR("Trying to present non-existen segment in cached color surface!");
return 0;
}
if ((start_sourced_line != 0) || (start_x != 0) || (info.width != width) || (info.height != height) || (info.format != base_format)) {
std::uint64_t current_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
std::vector<std::unique_ptr<GLCastedTexture>> &casted_vec = info.casted_textures;
GLenum source_format, source_data_type = 0;
if (state.features.preserve_f16_nan_as_u16 && color::is_write_surface_stored_rawly(info.format)) {
source_format = color::get_raw_store_upload_format_type(info.format);
source_data_type = color::get_raw_store_upload_data_type(info.format);
} else {
source_format = color::translate_format(info.format);
source_data_type = color::translate_type(info.format);
}
if ((base_format != info.format) || (info.height != height) || (info.width != width) || (ite->first != address.address())) {
// Look in cast cache and grab one. The cache really does not store immediate grab on now, but rather to reduce the synchronization in the pipeline (use different texture)
for (std::size_t i = 0; i < casted_vec.size();) {
if ((casted_vec[i]->cropped_height == height) && (casted_vec[i]->cropped_width == width) && (casted_vec[i]->cropped_y == start_sourced_line) && (casted_vec[i]->cropped_x == start_x) && (casted_vec[i]->format == base_format)) {
glBindTexture(GL_TEXTURE_2D, casted_vec[i]->texture[0]);
if (color::bytes_per_pixel_in_gl_storage(base_format) == color::bytes_per_pixel_in_gl_storage(info.format)) {
glCopyImageSubData(info.gl_texture[0], GL_TEXTURE_2D, 0, static_cast<GLint>(start_x), static_cast<GLint>(start_sourced_line), 0, casted_vec[i]->texture[0], GL_TEXTURE_2D,
0, 0, 0, 0, width, height, 1);
} else {
do_typeless_copy(casted_vec[i]->texture[0], info.gl_texture[0], surface_internal_format, surface_upload_format,
surface_data_type, source_format, source_data_type, static_cast<GLint>(start_x), static_cast<GLint>(start_sourced_line), info.width,
height, width, height, info.total_bytes);
}
casted_vec[i]->last_used_time = current_time;
return casted_vec[i]->texture[0];
} else {
if (current_time - info.casted_textures[i]->last_used_time >= CASTED_UNUSED_TEXTURE_PURGE_SECS) {
casted_vec.erase(casted_vec.begin() + i);
continue;
}
}
i++;
}
}
// Try to crop + cast
std::unique_ptr<GLCastedTexture> casted_info_unq = std::make_unique<GLCastedTexture>();
GLCastedTexture &casted_info = *casted_info_unq;
if (!casted_info.texture.init(reinterpret_cast<renderer::Generator *>(glGenTextures), reinterpret_cast<renderer::Deleter *>(glDeleteTextures))) {
LOG_ERROR("Failed to initialise cast color surface texture!");
return 0;
}
glBindTexture(GL_TEXTURE_2D, casted_info.texture[0]);
if (color::bytes_per_pixel_in_gl_storage(base_format) == color::bytes_per_pixel_in_gl_storage(info.format)) {
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, width, height, 0, surface_upload_format, surface_data_type, nullptr);
// Make it a complete texture (what kind of requirement is this)?
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glCopyImageSubData(info.gl_texture[0], GL_TEXTURE_2D, 0, static_cast<GLint>(start_x), static_cast<GLint>(start_sourced_line), 0, casted_info.texture[0], GL_TEXTURE_2D,
0, 0, 0, 0, width, height, 1);
} else {
// TODO: Copy sub region of typeless copy is still not handled ((
// We must do a typeless copy (RPCS3)
do_typeless_copy(casted_info.texture[0], info.gl_texture[0], surface_internal_format, surface_upload_format,
surface_data_type, source_format, source_data_type, static_cast<GLint>(start_x), static_cast<GLint>(start_sourced_line), info.width,
height, width, height, info.total_bytes);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
casted_info.format = base_format;
casted_info.cropped_x = start_x;
casted_info.cropped_y = start_sourced_line;
casted_info.cropped_width = width;
casted_info.cropped_height = height;
casted_info.last_used_time = current_time;
casted_vec.push_back(std::move(casted_info_unq));
return casted_info.texture[0];
} else {
if (state.features.preserve_f16_nan_as_u16 && color::is_write_surface_stored_rawly(info.format)) {
// Create a texture view
if (!info.gl_expected_read_texture_view[0]) {
if (!info.gl_expected_read_texture_view.init(reinterpret_cast<renderer::Generator *>(glGenTextures), reinterpret_cast<renderer::Deleter *>(glDeleteTextures))) {
LOG_ERROR("Unable to initialize texture view for casting texture!");
return 0;
}
glBindTexture(GL_TEXTURE_2D, info.gl_expected_read_texture_view[0]);
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, width, height, 0, surface_upload_format, surface_data_type, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
glCopyImageSubData(info.gl_texture[0], GL_TEXTURE_2D, 0, 0, 0, 0, info.gl_expected_read_texture_view[0], GL_TEXTURE_2D,
0, 0, 0, 0, width, height, 1);
return info.gl_expected_read_texture_view[0];
}
return info.gl_texture[0];
}
}
}
if (!invalidated) {
if (purpose == SurfaceTextureRetrievePurpose::WRITING) {
if (used_iterator != last_use_color_surface_index.end()) {
last_use_color_surface_index.erase(used_iterator);
}
last_use_color_surface_index.push_back(ite->first);
return info.gl_texture[0];
} else {
return 0;
}
} else if (used_iterator != last_use_color_surface_index.end()) {
last_use_color_surface_index.erase(used_iterator);
}
}
std::unique_ptr<GLColorSurfaceCacheInfo> info_added = std::make_unique<GLColorSurfaceCacheInfo>();
info_added->width = width;
info_added->height = height;
info_added->original_width = original_width;
info_added->original_height = original_height;
info_added->pixel_stride = pixel_stride;
info_added->data = address;
info_added->total_bytes = bytes_per_stride * original_height;
info_added->format = base_format;
info_added->swizzle = swizzle;
info_added->flags = 0;
if (!info_added->gl_texture.init(reinterpret_cast<renderer::Generator *>(glGenTextures), reinterpret_cast<renderer::Deleter *>(glDeleteTextures))) {
LOG_ERROR("Failed to initialise color surface texture!");
color_surface_textures.erase(key);
return 0;
}
GLint texture_handle_return = info_added->gl_texture[0];
bool store_rawly = false;
if (state.features.preserve_f16_nan_as_u16 && color::is_write_surface_stored_rawly(base_format)) {
surface_internal_format = color::get_raw_store_internal_type(base_format);
surface_upload_format = color::get_raw_store_upload_format_type(base_format);
surface_data_type = color::get_raw_store_upload_data_type(base_format);
store_rawly = true;
}
glBindTexture(GL_TEXTURE_2D, texture_handle_return);
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, width, height, 0, surface_upload_format, surface_data_type, nullptr);
if (!store_rawly) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
}
if (color_surface_textures.count(key) > 0) {
static bool has_happened = false;
LOG_WARN_IF(!has_happened, "Two different surfaces have the same base adress, this is not handled, an openGL error will happen.");
has_happened = true;
}
color_surface_textures.emplace(key, std::move(info_added));
// Now that everything goes well, we can start rearranging
if (last_use_color_surface_index.size() >= MAX_CACHE_SIZE_PER_CONTAINER) {
// We have to purge a cache along with framebuffer
// So choose the one that is last used
const std::uint64_t first_key = last_use_color_surface_index.front();
GLuint texture_handle = color_surface_textures.find(first_key)->second->gl_texture[0];
for (auto it = framebuffer_array.cbegin(); it != framebuffer_array.cend();) {
if ((it->first & 0xFFFFFFFF) == texture_handle) {
it = framebuffer_array.erase(it);
} else {
++it;
}
}
last_use_color_surface_index.erase(last_use_color_surface_index.begin());
color_surface_textures.erase(first_key);
}
last_use_color_surface_index.push_back(key);
if (stored_height) {
*stored_height = height;
}
if (stored_width) {
*stored_width = width;
}
return texture_handle_return;
}
GLuint GLSurfaceCache::retrieve_ping_pong_color_surface_texture_handle(Ptr<void> address) {
auto ite = color_surface_textures.find(address.address());
if (ite == color_surface_textures.end()) {
return 0;
}
GLColorSurfaceCacheInfo &info = *ite->second;
GLenum surface_internal_format = color::translate_internal_format(info.format);
GLenum surface_upload_format = color::translate_format(info.format);
GLenum surface_data_type = color::translate_type(info.format);
if (!info.gl_ping_pong_texture[0]) {
if (!info.gl_ping_pong_texture.init(reinterpret_cast<renderer::Generator *>(glGenTextures), reinterpret_cast<renderer::Deleter *>(glDeleteTextures))) {
LOG_ERROR("Failed to initialise ping pong surface texture!");
return 0;
}
glBindTexture(GL_TEXTURE_2D, info.gl_ping_pong_texture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, surface_internal_format, info.width, info.height, 0, surface_upload_format, surface_data_type, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
} else {
glBindTexture(GL_TEXTURE_2D, info.gl_ping_pong_texture[0]);
}
glCopyImageSubData(info.gl_texture[0], GL_TEXTURE_2D, 0, 0, 0, 0, info.gl_ping_pong_texture[0], GL_TEXTURE_2D, 0, 0, 0, 0, info.width, info.height, 1);
return info.gl_ping_pong_texture[0];
}
GLuint GLSurfaceCache::retrieve_depth_stencil_texture_handle(const State &state, const MemState &mem, const SceGxmDepthStencilSurface &surface, std::int32_t force_width, std::int32_t force_height, const bool is_reading) {
if (!target) {
LOG_ERROR("Unable to retrieve Depth Stencil texture with no active render target!");
return 0;
}
force_width *= state.res_multiplier;
force_height *= state.res_multiplier;
bool packed_ds = surface.get_format() == SCE_GXM_DEPTH_STENCIL_FORMAT_S8D24;
if (force_width < 0) {
force_width = target->width;
}
if (force_height < 0) {
force_height = target->height;
}
const bool is_stencil_only = surface.depth_data.address() == 0;
std::size_t found_index = static_cast<std::size_t>(-1);
// The whole depth stencil struct is reserved for future use
for (std::size_t i = 0; i < depth_stencil_textures.size(); i++) {
if ((!is_stencil_only && depth_stencil_textures[i].surface.depth_data == surface.depth_data)
|| (is_stencil_only && depth_stencil_textures[i].surface.stencil_data == surface.stencil_data)) {
found_index = i;
break;
}
}
if (found_index != static_cast<std::size_t>(-1)) {
auto ite = std::find(last_use_depth_stencil_surface_index.begin(), last_use_depth_stencil_surface_index.end(), found_index);
if (ite != last_use_depth_stencil_surface_index.end()) {
last_use_depth_stencil_surface_index.erase(ite);
last_use_depth_stencil_surface_index.push_back(found_index);
}
GLDepthStencilSurfaceCacheInfo &cached_info = depth_stencil_textures[found_index];
bool need_remake = false;
if (cached_info.width < force_width) {
cached_info.width = force_width;
need_remake = true;
}
if (cached_info.height < force_height) {
cached_info.height = force_height;
need_remake = true;
}
if (need_remake) {
glBindTexture(GL_TEXTURE_2D, depth_stencil_textures[found_index].gl_texture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, cached_info.width, cached_info.height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
}
return depth_stencil_textures[found_index].gl_texture[0];
}
if (is_reading) {
return 0;
}
// Now that everything goes well, we can start rearranging
// Almost carbon copy but still too specific
if (last_use_depth_stencil_surface_index.size() >= MAX_CACHE_SIZE_PER_CONTAINER) {
// We have to purge a cache along with framebuffer
// So choose the one that is last used
const std::size_t index = last_use_depth_stencil_surface_index.front();
GLuint ds_texture_handle = depth_stencil_textures[index].gl_texture[0];
for (auto it = framebuffer_array.cbegin(); it != framebuffer_array.cend();) {
if (((it->first >> 32) & 0xFFFFFFFF) == ds_texture_handle) {
it = framebuffer_array.erase(it);
} else {
++it;
}
}
last_use_depth_stencil_surface_index.erase(last_use_depth_stencil_surface_index.begin());
depth_stencil_textures[index].flags = GLSurfaceCacheInfo::FLAG_FREE;
found_index = index;
}
if (found_index == static_cast<std::size_t>(-1)) {
// Still nowhere to found a free slot? We can search maybe
for (std::size_t i = 0; i < depth_stencil_textures.size(); i++) {
if (depth_stencil_textures[i].flags & GLSurfaceCacheInfo::FLAG_FREE) {
if (depth_stencil_textures[i].gl_texture[0] == 0) {
if (!depth_stencil_textures[i].gl_texture.init(reinterpret_cast<renderer::Generator *>(glGenTextures), reinterpret_cast<renderer::Deleter *>(glDeleteTextures))) {
LOG_ERROR("Fail to initialize depth stencil texture!");
return 0;
}
}
found_index = i;
break;
}
}
}
if (found_index == static_cast<std::size_t>(-1)) {
LOG_ERROR("No free depth stencil texture cache slot!");
return 0;
}
last_use_depth_stencil_surface_index.push_back(found_index);
depth_stencil_textures[found_index].flags = 0;
depth_stencil_textures[found_index].surface = surface;
depth_stencil_textures[found_index].width = force_width;
depth_stencil_textures[found_index].height = force_height;
glBindTexture(GL_TEXTURE_2D, depth_stencil_textures[found_index].gl_texture[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, force_width, force_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
return depth_stencil_textures[found_index].gl_texture[0];
}
GLuint GLSurfaceCache::retrieve_framebuffer_handle(const State &state, const MemState &mem, SceGxmColorSurface *color, SceGxmDepthStencilSurface *depth_stencil,
GLuint *color_texture_handle, GLuint *ds_texture_handle, std::uint16_t *stored_height) {
if (!target) {
LOG_ERROR("Unable to retrieve framebuffer with no active render target!");
return 0;
}
if (!color && !depth_stencil) {
LOG_ERROR("Depth stencil and color surface are both null!");
return 0;
}
GLuint color_handle = 0;
GLuint ds_handle = 0;
if (color) {
std::uint32_t swizzle_set = color->colorFormat & SCE_GXM_COLOR_SWIZZLE_MASK;
color_handle = static_cast<GLuint>(retrieve_color_surface_texture_handle(state, color->width,
color->height, color->strideInPixels, gxm::get_base_format(color->colorFormat), color->data,
renderer::SurfaceTextureRetrievePurpose::WRITING, swizzle_set, stored_height));
} else {
color_handle = target->attachments[0];
}
if (depth_stencil) {
ds_handle = static_cast<GLuint>(retrieve_depth_stencil_texture_handle(state, mem, *depth_stencil));
} else {
ds_handle = target->attachments[1];
}
const std::uint64_t key = (color_handle | (static_cast<std::uint64_t>(ds_handle) << 32));
auto ite = framebuffer_array.find(key);
if (ite != framebuffer_array.end()) {
if (color_texture_handle) {
*color_texture_handle = color_handle;
}
if (ds_texture_handle) {
*ds_texture_handle = ds_handle;
}
return ite->second[0];
}
// Create a new framebuffer for our sake
GLObjectArray<1> &fb = framebuffer_array[key];
if (!fb.init(reinterpret_cast<renderer::Generator *>(glGenFramebuffers), reinterpret_cast<renderer::Deleter *>(glDeleteFramebuffers))) {
LOG_ERROR("Can't initialize framebuffer!");
return 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, fb[0]);
if (color && state.features.preserve_f16_nan_as_u16 && renderer::gl::color::is_write_surface_stored_rawly(gxm::get_base_format(color->colorFormat))) {
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, color_handle, 0);
const GLenum buffers[] = { GL_NONE, GL_COLOR_ATTACHMENT1 };
glDrawBuffers(2, buffers);
} else {
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, color_handle, 0);
}
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, ds_handle, 0);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
LOG_ERROR("Framebuffer is not completed. Proceed anyway...");
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if (color_texture_handle) {
*color_texture_handle = color_handle;
}
if (ds_texture_handle) {
*ds_texture_handle = ds_handle;
}
return fb[0];
}
GLuint GLSurfaceCache::sourcing_color_surface_for_presentation(Ptr<const void> address, uint32_t width, uint32_t height, const std::uint32_t pitch, float *uvs, const int res_multiplier, SceFVector2 &texture_size) {
auto ite = color_surface_textures.lower_bound(address.address());
if (ite == color_surface_textures.end()) {
return 0;
}
width *= res_multiplier;
height *= res_multiplier;
const GLColorSurfaceCacheInfo &info = *ite->second;
if (info.pixel_stride == pitch) {
// In assumption the format is RGBA8
const std::size_t data_delta = address.address() - ite->first;
std::uint32_t limited_height = height;
if ((data_delta % (pitch * 4)) == 0) {
std::uint32_t start_sourced_line = (data_delta / (pitch * 4)) * res_multiplier;
if ((start_sourced_line + height) > info.height) {
// Sometimes the surface is just missing a little bit of lines
if (start_sourced_line < info.height) {
// Just limit the height and display it
limited_height = info.height - start_sourced_line;
} else {
LOG_ERROR("Trying to present non-existen segment in cached color surface!");
return 0;
}
}
// Calculate uvs
// First two top left, the two others bottom right
uvs[0] = 0.0f;
uvs[1] = static_cast<float>(start_sourced_line) / info.height;
uvs[2] = static_cast<float>(width) / info.width;
uvs[3] = static_cast<float>(start_sourced_line + limited_height) / info.height;
texture_size.x = info.width;
texture_size.y = info.height;
return info.gl_texture[0];
}
}
return 0;
}
} // namespace renderer::gl