rasterizer_cache: Introduce TextureRuntime and separate CachedSurface

* This commit aims to both continue the rasterizer cache cleanup by
  separating CachedSurface into a dedicated header and to start weeding
  out the raw OpenGL code from the cache.

* The latter is achieved by abstracting most texture operations in a new
  class called TextureRuntime. This has many benefits such as making it easier
  to port the functionality to other graphics APIs and the removal of the need
  to pass (read/draw) framebuffer handles everywhere. The filterer and
  reinterpreter get their own sets of FBOs due to this, something that
  might be a performance win since it reduces the state switching
  overhead on the runtime FBOs.
master
emufan4568 2022-08-21 00:50:20 +07:00
parent 199671301d
commit 17ad594a62
23 changed files with 1176 additions and 1012 deletions

@ -23,6 +23,8 @@ add_library(video_core STATIC
regs_texturing.h
renderer_base.cpp
renderer_base.h
rasterizer_cache/cached_surface.cpp
rasterizer_cache/cached_surface.h
rasterizer_cache/morton_swizzle.h
rasterizer_cache/pixel_format.h
rasterizer_cache/rasterizer_cache.cpp
@ -32,6 +34,8 @@ add_library(video_core STATIC
rasterizer_cache/rasterizer_cache_utils.h
rasterizer_cache/surface_params.cpp
rasterizer_cache/surface_params.h
rasterizer_cache/texture_runtime.cpp
rasterizer_cache/texture_runtime.h
renderer_opengl/frame_dumper_opengl.cpp
renderer_opengl/frame_dumper_opengl.h
renderer_opengl/gl_rasterizer.cpp

@ -0,0 +1,479 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/microprofile.h"
#include "common/texture.h"
#include "common/scope_exit.h"
#include "core/core.h"
#include "video_core/rasterizer_cache/cached_surface.h"
#include "video_core/rasterizer_cache/morton_swizzle.h"
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_downloader_es.h"
namespace OpenGL {
static Aspect ToAspect(SurfaceType type) {
switch (type) {
case SurfaceType::Color:
case SurfaceType::Texture:
case SurfaceType::Fill:
return Aspect::Color;
case SurfaceType::Depth:
return Aspect::Depth;
case SurfaceType::DepthStencil:
return Aspect::DepthStencil;
default:
LOG_CRITICAL(Render_OpenGL, "Unknown SurfaceType {}", type);
UNREACHABLE();
}
return Aspect::Color;
}
CachedSurface::~CachedSurface() {
if (texture.handle) {
auto tag = is_custom ? HostTextureTag{GetFormatTuple(PixelFormat::RGBA8),
custom_tex_info.width, custom_tex_info.height}
: HostTextureTag{GetFormatTuple(pixel_format), GetScaledWidth(),
GetScaledHeight()};
owner.host_texture_recycler.emplace(tag, std::move(texture));
}
}
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64));
void CachedSurface::LoadGLBuffer(PAddr load_start, PAddr load_end) {
ASSERT(type != SurfaceType::Fill);
const bool need_swap =
GLES && (pixel_format == PixelFormat::RGBA8 || pixel_format == PixelFormat::RGB8);
const u8* const texture_src_data = VideoCore::g_memory->GetPhysicalPointer(addr);
if (texture_src_data == nullptr)
return;
if (gl_buffer.empty()) {
gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format));
}
// TODO: Should probably be done in ::Memory:: and check for other regions too
if (load_start < Memory::VRAM_VADDR_END && load_end > Memory::VRAM_VADDR_END)
load_end = Memory::VRAM_VADDR_END;
if (load_start < Memory::VRAM_VADDR && load_end > Memory::VRAM_VADDR)
load_start = Memory::VRAM_VADDR;
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
ASSERT(load_start >= addr && load_end <= end);
const u32 start_offset = load_start - addr;
if (!is_tiled) {
ASSERT(type == SurfaceType::Color);
if (need_swap) {
// TODO(liushuyu): check if the byteswap here is 100% correct
// cannot fully test this
if (pixel_format == PixelFormat::RGBA8) {
for (std::size_t i = start_offset; i < load_end - addr; i += 4) {
gl_buffer[i] = texture_src_data[i + 3];
gl_buffer[i + 1] = texture_src_data[i + 2];
gl_buffer[i + 2] = texture_src_data[i + 1];
gl_buffer[i + 3] = texture_src_data[i];
}
} else if (pixel_format == PixelFormat::RGB8) {
for (std::size_t i = start_offset; i < load_end - addr; i += 3) {
gl_buffer[i] = texture_src_data[i + 2];
gl_buffer[i + 1] = texture_src_data[i + 1];
gl_buffer[i + 2] = texture_src_data[i];
}
}
} else {
std::memcpy(&gl_buffer[start_offset], texture_src_data + start_offset,
load_end - load_start);
}
} else {
if (type == SurfaceType::Texture) {
Pica::Texture::TextureInfo tex_info{};
tex_info.width = width;
tex_info.height = height;
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(pixel_format);
tex_info.SetDefaultStride();
tex_info.physical_address = addr;
const SurfaceInterval load_interval(load_start, load_end);
const auto rect = GetSubRect(FromInterval(load_interval));
ASSERT(FromInterval(load_interval).GetInterval() == load_interval);
for (unsigned y = rect.bottom; y < rect.top; ++y) {
for (unsigned x = rect.left; x < rect.right; ++x) {
auto vec4 =
Pica::Texture::LookupTexture(texture_src_data, x, height - 1 - y, tex_info);
const std::size_t offset = (x + (width * y)) * 4;
std::memcpy(&gl_buffer[offset], vec4.AsArray(), 4);
}
}
} else {
morton_to_gl_fns[static_cast<std::size_t>(pixel_format)](stride, height, &gl_buffer[0],
addr, load_start, load_end);
}
}
}
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer(PAddr flush_start, PAddr flush_end) {
u8* const dst_buffer = VideoCore::g_memory->GetPhysicalPointer(addr);
if (dst_buffer == nullptr)
return;
ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format));
// TODO: Should probably be done in ::Memory:: and check for other regions too
// same as loadglbuffer()
if (flush_start < Memory::VRAM_VADDR_END && flush_end > Memory::VRAM_VADDR_END)
flush_end = Memory::VRAM_VADDR_END;
if (flush_start < Memory::VRAM_VADDR && flush_end > Memory::VRAM_VADDR)
flush_start = Memory::VRAM_VADDR;
MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
ASSERT(flush_start >= addr && flush_end <= end);
const u32 start_offset = flush_start - addr;
const u32 end_offset = flush_end - addr;
if (type == SurfaceType::Fill) {
const u32 coarse_start_offset = start_offset - (start_offset % fill_size);
const u32 backup_bytes = start_offset % fill_size;
std::array<u8, 4> backup_data;
if (backup_bytes)
std::memcpy(&backup_data[0], &dst_buffer[coarse_start_offset], backup_bytes);
for (u32 offset = coarse_start_offset; offset < end_offset; offset += fill_size) {
std::memcpy(&dst_buffer[offset], &fill_data[0],
std::min(fill_size, end_offset - offset));
}
if (backup_bytes)
std::memcpy(&dst_buffer[coarse_start_offset], &backup_data[0], backup_bytes);
} else if (!is_tiled) {
ASSERT(type == SurfaceType::Color);
if (pixel_format == PixelFormat::RGBA8 && GLES) {
for (std::size_t i = start_offset; i < flush_end - addr; i += 4) {
dst_buffer[i] = gl_buffer[i + 3];
dst_buffer[i + 1] = gl_buffer[i + 2];
dst_buffer[i + 2] = gl_buffer[i + 1];
dst_buffer[i + 3] = gl_buffer[i];
}
} else if (pixel_format == PixelFormat::RGB8 && GLES) {
for (std::size_t i = start_offset; i < flush_end - addr; i += 3) {
dst_buffer[i] = gl_buffer[i + 2];
dst_buffer[i + 1] = gl_buffer[i + 1];
dst_buffer[i + 2] = gl_buffer[i];
}
} else {
std::memcpy(dst_buffer + start_offset, &gl_buffer[start_offset],
flush_end - flush_start);
}
} else {
gl_to_morton_fns[static_cast<std::size_t>(pixel_format)](stride, height, &gl_buffer[0],
addr, flush_start, flush_end);
}
}
bool CachedSurface::LoadCustomTexture(u64 tex_hash) {
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
if (custom_tex_cache.IsTextureCached(tex_hash)) {
custom_tex_info = custom_tex_cache.LookupTexture(tex_hash);
return true;
}
if (!custom_tex_cache.CustomTextureExists(tex_hash)) {
return false;
}
const auto& path_info = custom_tex_cache.LookupTexturePathInfo(tex_hash);
if (!image_interface->DecodePNG(custom_tex_info.tex, custom_tex_info.width,
custom_tex_info.height, path_info.path)) {
LOG_ERROR(Render_OpenGL, "Failed to load custom texture {}", path_info.path);
return false;
}
const std::bitset<32> width_bits(custom_tex_info.width);
const std::bitset<32> height_bits(custom_tex_info.height);
if (width_bits.count() != 1 || height_bits.count() != 1) {
LOG_ERROR(Render_OpenGL, "Texture {} size is not a power of 2", path_info.path);
return false;
}
LOG_DEBUG(Render_OpenGL, "Loaded custom texture from {}", path_info.path);
Common::FlipRGBA8Texture(custom_tex_info.tex, custom_tex_info.width, custom_tex_info.height);
custom_tex_cache.CacheTexture(tex_hash, custom_tex_info.tex, custom_tex_info.width,
custom_tex_info.height);
return true;
}
void CachedSurface::DumpTexture(GLuint target_tex, u64 tex_hash) {
// Make sure the texture size is a power of 2
// If not, the surface is actually a framebuffer
std::bitset<32> width_bits(width);
std::bitset<32> height_bits(height);
if (width_bits.count() != 1 || height_bits.count() != 1) {
LOG_WARNING(Render_OpenGL, "Not dumping {:016X} because size isn't a power of 2 ({}x{})",
tex_hash, width, height);
return;
}
// Dump texture to RGBA8 and encode as PNG
const auto& image_interface = Core::System::GetInstance().GetImageInterface();
auto& custom_tex_cache = Core::System::GetInstance().CustomTexCache();
std::string dump_path =
fmt::format("{}textures/{:016X}/", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id);
if (!FileUtil::CreateFullPath(dump_path)) {
LOG_ERROR(Render, "Unable to create {}", dump_path);
return;
}
dump_path += fmt::format("tex1_{}x{}_{:016X}_{}.png", width, height, tex_hash, pixel_format);
if (!custom_tex_cache.IsTextureDumped(tex_hash) && !FileUtil::Exists(dump_path)) {
custom_tex_cache.SetTextureDumped(tex_hash);
LOG_INFO(Render_OpenGL, "Dumping texture to {}", dump_path);
std::vector<u8> decoded_texture;
decoded_texture.resize(width * height * 4);
OpenGLState state = OpenGLState::GetCurState();
GLuint old_texture = state.texture_units[0].texture_2d;
state.Apply();
/*
GetTexImageOES is used even if not using OpenGL ES to work around a small issue that
happens if using custom textures with texture dumping at the same.
Let's say there's 2 textures that are both 32x32 and one of them gets replaced with a
higher quality 256x256 texture. If the 256x256 texture is displayed first and the
32x32 texture gets uploaded to the same underlying OpenGL texture, the 32x32 texture
will appear in the corner of the 256x256 texture. If texture dumping is enabled and
the 32x32 is undumped, Citra will attempt to dump it. Since the underlying OpenGL
texture is still 256x256, Citra crashes because it thinks the texture is only 32x32.
GetTexImageOES conveniently only dumps the specified region, and works on both
desktop and ES.
*/
// if the backend isn't OpenGL ES, this won't be initialized yet
if (!owner.texture_downloader_es) {
owner.texture_downloader_es = std::make_unique<TextureDownloaderES>(false);
}
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE,
height, width, &decoded_texture[0]);
state.texture_units[0].texture_2d = old_texture;
state.Apply();
Common::FlipRGBA8Texture(decoded_texture, width, height);
if (!image_interface->EncodePNG(dump_path, decoded_texture, width, height))
LOG_ERROR(Render_OpenGL, "Failed to save decoded texture");
}
}
MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 192, 64));
void CachedSurface::UploadGLTexture(Common::Rectangle<u32> rect) {
if (type == SurfaceType::Fill) {
return;
}
MICROPROFILE_SCOPE(OpenGL_TextureUL);
ASSERT(gl_buffer.size() == width * height * GetBytesPerPixel(pixel_format));
u64 tex_hash = 0;
if (Settings::values.dump_textures || Settings::values.custom_textures) {
tex_hash = Common::ComputeHash64(gl_buffer.data(), gl_buffer.size());
}
if (Settings::values.custom_textures) {
is_custom = LoadCustomTexture(tex_hash);
}
// Load data from memory to the surface
GLint x0 = static_cast<GLint>(rect.left);
GLint y0 = static_cast<GLint>(rect.bottom);
std::size_t buffer_offset = (y0 * stride + x0) * GetBytesPerPixel(pixel_format);
const FormatTuple& tuple = GetFormatTuple(pixel_format);
GLuint target_tex = texture.handle;
// If not 1x scale, create 1x texture that we will blit from to replace texture subrect in
// surface
OGLTexture unscaled_tex;
if (res_scale != 1) {
x0 = 0;
y0 = 0;
if (is_custom) {
const auto& tuple = GetFormatTuple(PixelFormat::RGBA8);
unscaled_tex = owner.AllocateSurfaceTexture(tuple, custom_tex_info.width,
custom_tex_info.height);
} else {
unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(), rect.GetHeight());
}
target_tex = unscaled_tex.handle;
}
OpenGLState cur_state = OpenGLState::GetCurState();
GLuint old_tex = cur_state.texture_units[0].texture_2d;
cur_state.texture_units[0].texture_2d = target_tex;
cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
if (is_custom) {
if (res_scale == 1) {
texture = owner.AllocateSurfaceTexture(GetFormatTuple(PixelFormat::RGBA8),
custom_tex_info.width, custom_tex_info.height);
cur_state.texture_units[0].texture_2d = texture.handle;
cur_state.Apply();
}
// Always going to be using rgba8
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(custom_tex_info.width));
glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, custom_tex_info.width, custom_tex_info.height,
GL_RGBA, GL_UNSIGNED_BYTE, custom_tex_info.tex.data());
} else {
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
glActiveTexture(GL_TEXTURE0);
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
&gl_buffer[buffer_offset]);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (Settings::values.dump_textures && !is_custom) {
DumpTexture(target_tex, tex_hash);
}
cur_state.texture_units[0].texture_2d = old_tex;
cur_state.Apply();
if (res_scale != 1) {
auto scaled_rect = rect;
scaled_rect.left *= res_scale;
scaled_rect.top *= res_scale;
scaled_rect.right *= res_scale;
scaled_rect.bottom *= res_scale;
const u32 width = is_custom ? custom_tex_info.width : rect.GetWidth();
const u32 height = is_custom ? custom_tex_info.height : rect.GetHeight();
const Common::Rectangle<u32> from_rect{0, height, width, 0};
if (!owner.texture_filterer->Filter(unscaled_tex, from_rect, texture, scaled_rect, type)) {
const Aspect aspect = ToAspect(type);
runtime.BlitTextures(unscaled_tex, {aspect, from_rect},
texture, {aspect, scaled_rect});
}
}
InvalidateAllWatcher();
}
MICROPROFILE_DEFINE(OpenGL_TextureDL, "OpenGL", "Texture Download", MP_RGB(128, 192, 64));
void CachedSurface::DownloadGLTexture(const Common::Rectangle<u32>& rect) {
if (type == SurfaceType::Fill) {
return;
}
MICROPROFILE_SCOPE(OpenGL_TextureDL);
if (gl_buffer.empty()) {
gl_buffer.resize(width * height * GetBytesPerPixel(pixel_format));
}
OpenGLState state = OpenGLState::GetCurState();
OpenGLState prev_state = state;
SCOPE_EXIT({ prev_state.Apply(); });
const FormatTuple& tuple = GetFormatTuple(pixel_format);
// Ensure no bad interactions with GL_PACK_ALIGNMENT
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
const std::size_t buffer_offset = (rect.bottom * stride + rect.left) * GetBytesPerPixel(pixel_format);
// If not 1x scale, blit scaled texture to a new 1x texture and use that to flush
const Aspect aspect = ToAspect(type);
if (res_scale != 1) {
auto scaled_rect = rect;
scaled_rect.left *= res_scale;
scaled_rect.top *= res_scale;
scaled_rect.right *= res_scale;
scaled_rect.bottom *= res_scale;
const Common::Rectangle<u32> unscaled_tex_rect{0, rect.GetHeight(), rect.GetWidth(), 0};
auto unscaled_tex = owner.AllocateSurfaceTexture(tuple, rect.GetWidth(),
rect.GetHeight());
// Blit scaled texture to the unscaled one
runtime.BlitTextures(texture, {aspect, scaled_rect},
unscaled_tex, {aspect, unscaled_tex_rect});
state.texture_units[0].texture_2d = unscaled_tex.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
if (GLES) {
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type,
rect.GetHeight(), rect.GetWidth(),
&gl_buffer[buffer_offset]);
} else {
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, &gl_buffer[buffer_offset]);
}
} else {
runtime.ReadTexture(texture, {aspect, rect}, tuple, gl_buffer.data());
}
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
bool CachedSurface::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const {
if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
boost::icl::first(fill_interval) >= addr &&
boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range
dest_surface.FromInterval(fill_interval).GetInterval() ==
fill_interval) { // make sure interval is a rectangle in dest surface
if (fill_size * 8 != dest_surface.GetFormatBpp()) {
// Check if bits repeat for our fill_size
const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u);
std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel);
for (u32 i = 0; i < dest_bytes_per_pixel; ++i)
std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size);
for (u32 i = 0; i < fill_size; ++i)
if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0],
dest_bytes_per_pixel) != 0)
return false;
if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4))
return false;
}
return true;
}
return false;
}
bool CachedSurface::CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const {
SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval);
ASSERT(subrect_params.GetInterval() == copy_interval);
if (CanSubRect(subrect_params))
return true;
if (CanFill(dest_surface, copy_interval))
return true;
return false;
}
} // namespace OpenGL

@ -0,0 +1,136 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/assert.h"
#include "core/custom_tex_cache.h"
#include "video_core/rasterizer_cache/surface_params.h"
#include "video_core/rasterizer_cache/texture_runtime.h"
namespace OpenGL {
/**
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
* surface collection objects, including texture cube and mipmap.
*/
class SurfaceWatcher {
friend class CachedSurface;
public:
explicit SurfaceWatcher(std::weak_ptr<CachedSurface>&& surface) :
surface(std::move(surface)) {}
/// Checks whether the surface has been changed.
bool IsValid() const {
return !surface.expired() && valid;
}
/// Marks that the content of the referencing surface has been updated to the watcher user.
void Validate() {
ASSERT(!surface.expired());
valid = true;
}
/// Gets the referencing surface. Returns null if the surface has been destroyed
Surface Get() const {
return surface.lock();
}
private:
std::weak_ptr<CachedSurface> surface;
bool valid = false;
};
class RasterizerCacheOpenGL;
class CachedSurface : public SurfaceParams, public std::enable_shared_from_this<CachedSurface> {
public:
CachedSurface(RasterizerCacheOpenGL& owner, TextureRuntime& runtime) :
owner(owner), runtime(runtime) {}
~CachedSurface();
/// Read/Write data in 3DS memory to/from gl_buffer
void LoadGLBuffer(PAddr load_start, PAddr load_end);
void FlushGLBuffer(PAddr flush_start, PAddr flush_end);
/// Custom texture loading and dumping
bool LoadCustomTexture(u64 tex_hash);
void DumpTexture(GLuint target_tex, u64 tex_hash);
/// Upload/Download data in gl_buffer in/to this surface's texture
void UploadGLTexture(Common::Rectangle<u32> rect);
void DownloadGLTexture(const Common::Rectangle<u32>& rect);
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
bool IsRegionValid(SurfaceInterval interval) const {
return (invalid_regions.find(interval) == invalid_regions.end());
}
bool IsSurfaceFullyInvalid() const {
auto interval = GetInterval();
return *invalid_regions.equal_range(interval).first == interval;
}
std::shared_ptr<SurfaceWatcher> CreateWatcher() {
auto watcher = std::make_shared<SurfaceWatcher>(weak_from_this());
watchers.push_front(watcher);
return watcher;
}
void InvalidateAllWatcher() {
for (const auto& watcher : watchers) {
if (auto locked = watcher.lock()) {
locked->valid = false;
}
}
}
void UnlinkAllWatcher() {
for (const auto& watcher : watchers) {
if (auto locked = watcher.lock()) {
locked->valid = false;
locked->surface.reset();
}
}
watchers.clear();
}
public:
bool registered = false;
SurfaceRegions invalid_regions;
std::vector<u8> gl_buffer;
// Number of bytes to read from fill_data
u32 fill_size = 0;
std::array<u8, 4> fill_data;
OGLTexture texture;
// level_watchers[i] watches the (i+1)-th level mipmap source surface
std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
u32 max_level = 0;
// Information about custom textures
bool is_custom = false;
Core::CustomTexInfo custom_tex_info;
private:
RasterizerCacheOpenGL& owner;
TextureRuntime& runtime;
std::list<std::weak_ptr<SurfaceWatcher>> watchers;
};
struct CachedTextureCube {
OGLTexture texture;
u16 res_scale = 1;
std::shared_ptr<SurfaceWatcher> px;
std::shared_ptr<SurfaceWatcher> nx;
std::shared_ptr<SurfaceWatcher> py;
std::shared_ptr<SurfaceWatcher> ny;
std::shared_ptr<SurfaceWatcher> pz;
std::shared_ptr<SurfaceWatcher> nz;
};
} // namespace OpenGL

@ -10,6 +10,8 @@
namespace OpenGL {
constexpr u32 PIXEL_FORMAT_COUNT = 18;
enum class PixelFormat : u8 {
// First 5 formats are shared between textures and color buffers
RGBA8 = 0,
@ -43,7 +45,7 @@ enum class SurfaceType {
Invalid = 5
};
static constexpr std::string_view PixelFormatAsString(PixelFormat format) {
inline constexpr std::string_view PixelFormatAsString(PixelFormat format) {
switch (format) {
case PixelFormat::RGBA8:
return "RGBA8";
@ -84,23 +86,23 @@ static constexpr std::string_view PixelFormatAsString(PixelFormat format) {
}
}
static constexpr PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
inline constexpr PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
const u32 format_index = static_cast<u32>(format);
return (format_index < 14) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
}
static constexpr PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
inline constexpr PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
const u32 format_index = static_cast<u32>(format);
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
}
static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
inline PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
const u32 format_index = static_cast<u32>(format);
return (format_index < 4) ? static_cast<PixelFormat>(format_index + 14)
: PixelFormat::Invalid;
}
static constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
inline constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
switch (format) {
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
case GPU::Regs::PixelFormat::RGB565:
@ -133,7 +135,7 @@ static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
return SurfaceType::Invalid;
}
static constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
inline constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
SurfaceType source_type = GetFormatType(source_format);
SurfaceType dest_type = GetFormatType(dest_format);
@ -182,7 +184,7 @@ static constexpr u32 GetFormatBpp(PixelFormat format) {
}
}
static constexpr u32 GetBytesPerPixel(PixelFormat format) {
inline constexpr u32 GetBytesPerPixel(PixelFormat format) {
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
if (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture) {
return 4;

File diff suppressed because it is too large Load Diff

@ -4,11 +4,9 @@
#pragma once
#include <unordered_map>
#include "common/assert.h"
#include "core/custom_tex_cache.h"
#include "video_core/rasterizer_cache/cached_surface.h"
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
#include "video_core/rasterizer_cache/surface_params.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/texture/texture_decode.h"
namespace OpenGL {
@ -19,129 +17,6 @@ enum class ScaleMatch {
Ignore // accept every scaled res
};
/**
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
* surface collection objects, including texture cube and mipmap.
*/
struct SurfaceWatcher {
public:
explicit SurfaceWatcher(std::weak_ptr<CachedSurface>&& surface) : surface(std::move(surface)) {}
/**
* Checks whether the surface has been changed.
* @return false if the surface content has been changed since last Validate() call or has been
* destroyed; otherwise true
*/
bool IsValid() const {
return !surface.expired() && valid;
}
/// Marks that the content of the referencing surface has been updated to the watcher user.
void Validate() {
ASSERT(!surface.expired());
valid = true;
}
/// Gets the referencing surface. Returns null if the surface has been destroyed
Surface Get() const {
return surface.lock();
}
private:
friend struct CachedSurface;
std::weak_ptr<CachedSurface> surface;
bool valid = false;
};
class RasterizerCacheOpenGL;
struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface> {
CachedSurface(RasterizerCacheOpenGL& owner) : owner{owner} {}
~CachedSurface();
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
bool IsRegionValid(SurfaceInterval interval) const {
return (invalid_regions.find(interval) == invalid_regions.end());
}
bool IsSurfaceFullyInvalid() const {
auto interval = GetInterval();
return *invalid_regions.equal_range(interval).first == interval;
}
bool registered = false;
SurfaceRegions invalid_regions;
u32 fill_size = 0; /// Number of bytes to read from fill_data
std::array<u8, 4> fill_data;
OGLTexture texture;
/// max mipmap level that has been attached to the texture
u32 max_level = 0;
/// level_watchers[i] watches the (i+1)-th level mipmap source surface
std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
bool is_custom = false;
Core::CustomTexInfo custom_tex_info;
std::vector<u8> gl_buffer;
// Read/Write data in 3DS memory to/from gl_buffer
void LoadGLBuffer(PAddr load_start, PAddr load_end);
void FlushGLBuffer(PAddr flush_start, PAddr flush_end);
// Custom texture loading and dumping
bool LoadCustomTexture(u64 tex_hash);
void DumpTexture(GLuint target_tex, u64 tex_hash);
// Upload/Download data in gl_buffer in/to this surface's texture
void UploadGLTexture(Common::Rectangle<u32> rect, GLuint read_fb_handle, GLuint draw_fb_handle);
void DownloadGLTexture(const Common::Rectangle<u32>& rect, GLuint read_fb_handle,
GLuint draw_fb_handle);
std::shared_ptr<SurfaceWatcher> CreateWatcher() {
auto watcher = std::make_shared<SurfaceWatcher>(weak_from_this());
watchers.push_front(watcher);
return watcher;
}
void InvalidateAllWatcher() {
for (const auto& watcher : watchers) {
if (auto locked = watcher.lock()) {
locked->valid = false;
}
}
}
void UnlinkAllWatcher() {
for (const auto& watcher : watchers) {
if (auto locked = watcher.lock()) {
locked->valid = false;
locked->surface.reset();
}
}
watchers.clear();
}
private:
RasterizerCacheOpenGL& owner;
std::list<std::weak_ptr<SurfaceWatcher>> watchers;
};
struct CachedTextureCube {
OGLTexture texture;
u16 res_scale = 1;
std::shared_ptr<SurfaceWatcher> px;
std::shared_ptr<SurfaceWatcher> nx;
std::shared_ptr<SurfaceWatcher> py;
std::shared_ptr<SurfaceWatcher> ny;
std::shared_ptr<SurfaceWatcher> pz;
std::shared_ptr<SurfaceWatcher> nz;
};
class TextureDownloaderES;
class TextureFilterer;
class FormatReinterpreterOpenGL;
@ -233,14 +108,12 @@ private:
/// Increase/decrease the number of surface in pages touching the specified region
void UpdatePagesCachedCount(PAddr addr, u32 size, int delta);
TextureRuntime runtime;
SurfaceCache surface_cache;
PageMap cached_pages;
SurfaceMap dirty_regions;
SurfaceSet remove_surfaces;
OGLFramebuffer read_framebuffer;
OGLFramebuffer draw_framebuffer;
u16 resolution_scale_factor;
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;

@ -13,7 +13,7 @@
namespace OpenGL {
struct CachedSurface;
class CachedSurface;
using Surface = std::shared_ptr<CachedSurface>;
// Declare rasterizer interval types

@ -4,9 +4,6 @@
#pragma once
#include <functional>
#include <set>
#include <boost/icl/interval_map.hpp>
#include <boost/icl/interval_set.hpp>
#include "common/hash.h"
#include "video_core/rasterizer_cache/pixel_format.h"

@ -0,0 +1,196 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/scope_exit.h"
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
#include "video_core/rasterizer_cache/texture_runtime.h"
#include "video_core/renderer_opengl/gl_state.h"
namespace OpenGL {
GLbitfield ToBufferMask(Aspect aspect) {
switch (aspect) {
case Aspect::Color:
return GL_COLOR_BUFFER_BIT;
case Aspect::Depth:
return GL_DEPTH_BUFFER_BIT;
case Aspect::DepthStencil:
return GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
}
}
TextureRuntime::TextureRuntime() {
read_fbo.Create();
draw_fbo.Create();
}
void TextureRuntime::ReadTexture(const OGLTexture& tex, Subresource subresource,
const FormatTuple& tuple, u8* pixels) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.ResetTexture(tex.handle);
state.draw.read_framebuffer = read_fbo.handle;
state.Apply();
const u32 level = subresource.level;
switch (subresource.aspect) {
case Aspect::Color:
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
tex.handle, level);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
break;
case Aspect::Depth:
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
tex.handle, level);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
break;
case Aspect::DepthStencil:
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
tex.handle, level);
break;
}
const auto& rect = subresource.region;
glReadPixels(rect.left, rect.bottom, rect.GetWidth(), rect.GetHeight(),
tuple.format, tuple.type, pixels);
}
bool TextureRuntime::ClearTexture(const OGLTexture& tex, Subresource subresource,
ClearValue value) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
// Setup scissor rectangle according to the clear rectangle
const auto& clear_rect = subresource.region;
OpenGLState state;
state.scissor.enabled = true;
state.scissor.x = clear_rect.left;
state.scissor.y = clear_rect.bottom;
state.scissor.width = clear_rect.GetWidth();
state.scissor.height = clear_rect.GetHeight();
state.draw.draw_framebuffer = draw_fbo.handle;
state.Apply();
const u32 level = subresource.level;
switch (subresource.aspect) {
case Aspect::Color:
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
tex.handle, level);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
state.color_mask.red_enabled = true;
state.color_mask.green_enabled = true;
state.color_mask.blue_enabled = true;
state.color_mask.alpha_enabled = true;
state.Apply();
glClearBufferfv(GL_COLOR, 0, value.color.AsArray());
break;
case Aspect::Depth:
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
tex.handle, level);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
state.depth.write_mask = GL_TRUE;
state.Apply();
glClearBufferfv(GL_DEPTH, 0, &value.depth);
break;
case Aspect::DepthStencil:
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
tex.handle, level);
state.depth.write_mask = GL_TRUE;
state.stencil.write_mask = -1;
state.Apply();
glClearBufferfi(GL_DEPTH_STENCIL, 0, value.depth, value.stencil);
break;
}
return true;
}
bool TextureRuntime::CopyTextures(const OGLTexture& src_tex, Subresource src_subresource,
const OGLTexture& dst_tex, Subresource dst_subresource) {
return true;
}
bool TextureRuntime::BlitTextures(const OGLTexture& src_tex, Subresource src_subresource,
const OGLTexture& dst_tex, Subresource dst_subresource) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.draw.read_framebuffer = read_fbo.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.Apply();
auto BindAttachment = [src_level = src_subresource.level,
dst_level = dst_subresource.level](GLenum target, u32 src_tex,
u32 dst_tex) -> void {
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, target, GL_TEXTURE_2D, src_tex, src_level);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, target, GL_TEXTURE_2D, dst_tex, dst_level);
};
// Sanity check; Can't blit a color texture to a depth buffer
ASSERT(src_subresource.aspect == dst_subresource.aspect);
switch (src_subresource.aspect) {
case Aspect::Color:
// Bind only color
BindAttachment(GL_COLOR_ATTACHMENT0, src_tex.handle, dst_tex.handle);
BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, 0, 0);
break;
case Aspect::Depth:
// Bind only depth
BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0);
BindAttachment(GL_DEPTH_ATTACHMENT, src_tex.handle, dst_tex.handle);
BindAttachment(GL_STENCIL_ATTACHMENT, 0, 0);
break;
case Aspect::DepthStencil:
// Bind to combined depth + stencil
BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0);
BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, src_tex.handle, dst_tex.handle);
break;
}
// TODO (wwylele): use GL_NEAREST for shadow map texture
// Note: shadow map is treated as RGBA8 format in PICA, as well as in the rasterizer cache, but
// doing linear intepolation componentwise would cause incorrect value. However, for a
// well-programmed game this code path should be rarely executed for shadow map with
// inconsistent scale.
const GLenum filter = src_subresource.aspect == Aspect::Color ? GL_LINEAR : GL_NEAREST;
const auto& src_rect = src_subresource.region;
const auto& dst_rect = dst_subresource.region;
glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top,
dst_rect.left, dst_rect.bottom, dst_rect.right, dst_rect.top,
ToBufferMask(src_subresource.aspect), filter);
return true;
}
void TextureRuntime::GenerateMipmaps(const OGLTexture& tex, u32 max_level) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = tex.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level);
glGenerateMipmap(GL_TEXTURE_2D);
}
} // namespace OpenGL

@ -0,0 +1,73 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/math_util.h"
#include "common/vector_math.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
// Describes the type of data a texture holds
enum class Aspect {
Color = 0,
Depth = 1,
DepthStencil = 2
};
// A union for both color and depth/stencil clear values
union ClearValue {
Common::Vec4f color;
struct {
float depth;
u8 stencil;
};
};
struct Subresource {
Subresource(Aspect aspect, Common::Rectangle<u32> region, u32 level = 0, u32 layer = 0) :
aspect(aspect), region(region), level(level), layer(layer) {}
Aspect aspect;
Common::Rectangle<u32> region;
u32 level = 0;
u32 layer = 0;
};
struct FormatTuple;
/**
* Provides texture manipulation functions to the rasterizer cache
* Separating this into a class makes it easier to abstract graphics API code
*/
class TextureRuntime {
public:
TextureRuntime();
~TextureRuntime() = default;
// Copies the GPU pixel data to the provided pixels buffer
void ReadTexture(const OGLTexture& tex, Subresource subresource,
const FormatTuple& tuple, u8* pixels);
// Fills the rectangle of the texture with the clear value provided
bool ClearTexture(const OGLTexture& texture, Subresource subresource, ClearValue value);
// Copies a rectangle of src_tex to another rectange of dst_rect
// NOTE: The width and height of the rectangles must be equal
bool CopyTextures(const OGLTexture& src_tex, Subresource src_subresource,
const OGLTexture& dst_tex, Subresource dst_subresource);
// Copies a rectangle of src_tex to another rectange of dst_rect performing
// scaling and format conversions
bool BlitTextures(const OGLTexture& src_tex, Subresource src_subresource,
const OGLTexture& dst_tex, Subresource dst_subresource);
// Generates mipmaps for all the available levels of the texture
void GenerateMipmaps(const OGLTexture& tex, u32 max_level);
private:
OGLFramebuffer read_fbo, draw_fbo;
};
} // namespace OpenGL

@ -1,14 +1,12 @@
// Copyright 2020 Citra Emulator Project
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "common/scope_exit.h"
#include "video_core/renderer_opengl/gl_format_reinterpreter.h"
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
namespace OpenGL {
@ -64,15 +62,18 @@ void main() {
vao.Create();
}
void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle,
GLuint dst_tex, const Common::Rectangle<u32>& dst_rect,
GLuint draw_fb_handle) override {
PixelFormat GetSourceFormat() const override {
return PixelFormat::RGBA4;
}
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = src_tex;
state.draw.draw_framebuffer = draw_fb_handle;
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
@ -80,10 +81,10 @@ void main() {
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
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);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight());
glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight());
@ -148,15 +149,18 @@ void main() {
~PixelBufferD24S8toABGR() {}
void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle,
GLuint dst_tex, const Common::Rectangle<u32>& dst_rect,
GLuint draw_fb_handle) override {
PixelFormat GetSourceFormat() const override {
return PixelFormat::D24S8;
}
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.draw.read_framebuffer = read_fb_handle;
state.draw.draw_framebuffer = draw_fb_handle;
state.draw.read_framebuffer = read_fbo.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.Apply();
glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle);
@ -170,7 +174,8 @@ void main() {
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
src_tex, 0);
src_tex.handle, 0);
glReadPixels(static_cast<GLint>(src_rect.left), static_cast<GLint>(src_rect.bottom),
static_cast<GLsizei>(src_rect.GetWidth()),
static_cast<GLsizei>(src_rect.GetHeight()), GL_DEPTH_STENCIL,
@ -200,12 +205,11 @@ void main() {
static_cast<GLfloat>(state.viewport.width),
static_cast<GLfloat>(state.viewport.height));
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);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindTexture(GL_TEXTURE_BUFFER, 0);
}
@ -292,19 +296,22 @@ void main() {
}
}
void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint read_fb_handle,
GLuint dst_tex, const Common::Rectangle<u32>& dst_rect,
GLuint draw_fb_handle) override {
PixelFormat GetSourceFormat() const override {
return PixelFormat::D24S8;
}
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = src_tex;
state.texture_units[0].texture_2d = src_tex.handle;
if (use_texture_view) {
temp_tex.Create();
glActiveTexture(GL_TEXTURE1);
glTextureView(temp_tex.handle, GL_TEXTURE_2D, src_tex, GL_DEPTH24_STENCIL8, 0, 1, 0, 1);
glTextureView(temp_tex.handle, GL_TEXTURE_2D, src_tex.handle, GL_DEPTH24_STENCIL8, 0, 1, 0, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
} else if (src_rect.top > temp_rect.top || src_rect.right > temp_rect.right) {
@ -320,7 +327,7 @@ void main() {
}
state.texture_units[1].texture_2d = temp_tex.handle;
state.draw.draw_framebuffer = draw_fb_handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
@ -330,16 +337,16 @@ void main() {
glActiveTexture(GL_TEXTURE1);
if (!use_texture_view) {
glCopyImageSubData(src_tex, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
glCopyImageSubData(src_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
temp_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
src_rect.GetWidth(), src_rect.GetHeight(), 1);
}
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
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);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight());
glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight());
@ -363,32 +370,32 @@ private:
FormatReinterpreterOpenGL::FormatReinterpreterOpenGL() {
const std::string_view vendor{reinterpret_cast<const char*>(glGetString(GL_VENDOR))};
const std::string_view version{reinterpret_cast<const char*>(glGetString(GL_VERSION))};
// Fallback to PBO path on obsolete intel drivers
// intel`s GL_VERSION string - `3.3.0 - Build 25.20.100.6373`
const bool intel_broken_drivers =
vendor.find("Intel") != vendor.npos && (std::atoi(version.substr(14, 2).data()) < 30);
if ((!intel_broken_drivers && GLAD_GL_ARB_stencil_texturing && GLAD_GL_ARB_texture_storage &&
GLAD_GL_ARB_copy_image) ||
GLES) {
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8},
std::make_unique<ShaderD24S8toRGBA8>());
auto Register = [this](PixelFormat dest, std::unique_ptr<FormatReinterpreterBase>&& obj) {
const u32 dst_index = static_cast<u32>(dest);
return reinterpreters[dst_index].push_back(std::move(obj));
};
if ((!intel_broken_drivers && GLAD_GL_ARB_stencil_texturing &&
GLAD_GL_ARB_texture_storage && GLAD_GL_ARB_copy_image) || GLES) {
Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
LOG_INFO(Render_OpenGL, "Using shader for D24S8 to RGBA8 reinterpretation");
} else {
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGBA8, PixelFormat::D24S8},
std::make_unique<PixelBufferD24S8toABGR>());
LOG_INFO(Render_OpenGL, "Using pbo for D24S8 to RGBA8 reinterpretation");
Register(PixelFormat::RGBA8, std::make_unique<PixelBufferD24S8toABGR>());
LOG_INFO(Render_OpenGL, "Using PBO for D24S8 to RGBA8 reinterpretation");
}
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGB5A1, PixelFormat::RGBA4},
std::make_unique<RGBA4toRGB5A1>());
Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
}
FormatReinterpreterOpenGL::~FormatReinterpreterOpenGL() = default;
std::pair<FormatReinterpreterOpenGL::ReinterpreterMap::iterator,
FormatReinterpreterOpenGL::ReinterpreterMap::iterator>
FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format) {
return reinterpreters.equal_range(dst_format);
auto FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format)
-> const ReinterpreterList& {
return reinterpreters[static_cast<u32>(dst_format)];
}
} // namespace OpenGL

@ -1,62 +1,46 @@
// Copyright 2020 Citra Emulator Project
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <map>
#include <type_traits>
#include <glad/glad.h>
#include "common/common_types.h"
#include <unordered_map>
#include "common/math_util.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
class RasterizerCacheOpenGL;
struct PixelFormatPair {
const PixelFormat dst_format, src_format;
struct less {
using is_transparent = void;
constexpr bool operator()(PixelFormatPair lhs, PixelFormatPair rhs) const {
return std::tie(lhs.dst_format, lhs.src_format) <
std::tie(rhs.dst_format, rhs.src_format);
}
constexpr bool operator()(PixelFormat lhs, PixelFormatPair rhs) const {
return lhs < rhs.dst_format;
}
constexpr bool operator()(PixelFormatPair lhs, PixelFormat rhs) const {
return lhs.dst_format < rhs;
}
};
};
class FormatReinterpreterBase {
public:
FormatReinterpreterBase() {
read_fbo.Create();
draw_fbo.Create();
}
virtual ~FormatReinterpreterBase() = default;
virtual void Reinterpret(GLuint src_tex, const Common::Rectangle<u32>& src_rect,
GLuint read_fb_handle, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint draw_fb_handle) = 0;
virtual PixelFormat GetSourceFormat() const = 0;
virtual void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) = 0;
protected:
OGLFramebuffer read_fbo, draw_fbo;
};
using ReinterpreterList = std::vector<std::unique_ptr<FormatReinterpreterBase>>;
class FormatReinterpreterOpenGL : NonCopyable {
using ReinterpreterMap =
std::map<PixelFormatPair, std::unique_ptr<FormatReinterpreterBase>, PixelFormatPair::less>;
public:
explicit FormatReinterpreterOpenGL();
~FormatReinterpreterOpenGL();
FormatReinterpreterOpenGL();
~FormatReinterpreterOpenGL() = default;
auto GetPossibleReinterpretations(PixelFormat dst_format) ->
std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator>;
const ReinterpreterList& GetPossibleReinterpretations(PixelFormat dst_format);
private:
ReinterpreterMap reinterpreters;
std::array<ReinterpreterList, PIXEL_FORMAT_COUNT> reinterpreters;
};
} // namespace OpenGL

@ -30,7 +30,6 @@
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h"
#include "shaders/refine.frag"
@ -72,9 +71,8 @@ Anime4kUltrafast::Anime4kUltrafast(u16 scale_factor) : TextureFilterBase(scale_f
cur_state.Apply();
}
void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect,
GLuint dst_tex, const Common::Rectangle<u32>& dst_rect,
GLuint read_fb_handle, GLuint draw_fb_handle) {
void Anime4kUltrafast::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
// These will have handles from the previous texture that was filtered, reset them to avoid
@ -112,7 +110,7 @@ void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_
static_cast<GLint>(src_rect.bottom * internal_scale_factor),
static_cast<GLsizei>(src_rect.GetWidth() * internal_scale_factor),
static_cast<GLsizei>(src_rect.GetHeight() * internal_scale_factor)};
state.texture_units[0].texture_2d = src_tex;
state.texture_units[0].texture_2d = src_tex.handle;
state.texture_units[1].texture_2d = LUMAD.tex.handle;
state.texture_units[2].texture_2d = XY.tex.handle;
state.draw.draw_framebuffer = XY.fbo.handle;
@ -131,11 +129,12 @@ void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.draw.draw_framebuffer = draw_fb_handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.draw.shader_program = refine_program.handle;
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

@ -15,9 +15,8 @@ public:
static constexpr std::string_view NAME = "Anime4K Ultrafast";
explicit Anime4kUltrafast(u16 scale_factor);
void Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) override;
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
static constexpr u8 internal_scale_factor = 2;

@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/bicubic/bicubic.h"
#include "shaders/bicubic.frag"
@ -26,18 +25,18 @@ Bicubic::Bicubic(u16 scale_factor) : TextureFilterBase(scale_factor) {
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} // namespace OpenGL
void Bicubic::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) {
void Bicubic::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = src_tex;
state.draw.draw_framebuffer = draw_fb_handle;
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

@ -15,9 +15,8 @@ public:
static constexpr std::string_view NAME = "Bicubic";
explicit Bicubic(u16 scale_factor);
void Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) override;
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
OpenGLState state{};

@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/scale_force/scale_force.h"
#include "shaders/scale_force.frag"
@ -26,18 +25,18 @@ ScaleForce::ScaleForce(u16 scale_factor) : TextureFilterBase(scale_factor) {
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void ScaleForce::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) {
void ScaleForce::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = src_tex;
state.draw.draw_framebuffer = draw_fb_handle;
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

@ -15,9 +15,8 @@ public:
static constexpr std::string_view NAME = "ScaleForce";
explicit ScaleForce(u16 scale_factor);
void Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) override;
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
OpenGLState state{};

@ -3,23 +3,31 @@
// Refer to the license.txt file included.
#pragma once
#include <glad/glad.h>
#include <string_view>
#include "common/common_types.h"
#include "common/math_util.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
class TextureRuntime;
class OGLTexture;
class TextureFilterBase {
friend class TextureFilterer;
virtual void Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) = 0;
public:
explicit TextureFilterBase(u16 scale_factor) : scale_factor{scale_factor} {};
explicit TextureFilterBase(u16 scale_factor) : scale_factor(scale_factor) {
draw_fbo.Create();
};
virtual ~TextureFilterBase() = default;
private:
virtual void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) = 0;
protected:
OGLFramebuffer draw_fbo;
const u16 scale_factor{};
};

@ -58,16 +58,16 @@ bool TextureFilterer::IsNull() const {
return !filter;
}
bool TextureFilterer::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect,
SurfaceType type, GLuint read_fb_handle,
GLuint draw_fb_handle) {
// depth / stencil texture filtering is not supported for now
bool TextureFilterer::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect,
SurfaceType type) {
// Depth/Stencil texture filtering is not supported for now
if (IsNull() || (type != SurfaceType::Color && type != SurfaceType::Texture)) {
return false;
}
filter->Filter(src_tex, src_rect, dst_tex, dst_rect, read_fb_handle, draw_fb_handle);
filter->Filter(src_tex, src_rect, dst_tex, dst_rect);
return true;
}
@ -83,6 +83,7 @@ std::vector<std::string_view> TextureFilterer::GetFilterNames() {
return lhs_is_none && !rhs_is_none;
return lhs < rhs;
});
return ret;
}

@ -16,15 +16,19 @@ class TextureFilterer {
public:
static constexpr std::string_view NONE = "none";
public:
explicit TextureFilterer(std::string_view filter_name, u16 scale_factor);
// returns true if the filter actually changed
// Returns true if the filter actually changed
bool Reset(std::string_view new_filter_name, u16 new_scale_factor);
// returns true if there is no active filter
// Returns true if there is no active filter
bool IsNull() const;
// returns true if the texture was able to be filtered
bool Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, SurfaceType type,
GLuint read_fb_handle, GLuint draw_fb_handle);
// Returns true if the texture was able to be filtered
bool Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect,
SurfaceType type);
static std::vector<std::string_view> GetFilterNames();

@ -40,7 +40,6 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h"
#include "shaders/xbrz_freescale.frag"
@ -48,7 +47,9 @@
namespace OpenGL {
XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterBase(scale_factor) {
XbrzFreescale::XbrzFreescale(u16 scale_factor) :
TextureFilterBase(scale_factor) {
const OpenGLState cur_state = OpenGLState::GetCurState();
program.Create(xbrz_freescale_vert.data(), xbrz_freescale_frag.data());
@ -62,7 +63,9 @@ XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterBase(scale_factor)
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1f(glGetUniformLocation(program.handle, "scale"), static_cast<GLfloat>(scale_factor));
const GLint scale_loc = glGetUniformLocation(program.handle, "scale");
glUniform1f(scale_loc, static_cast<GLfloat>(scale_factor));
cur_state.Apply();
state.draw.vertex_array = vao.handle;
@ -70,19 +73,19 @@ XbrzFreescale::XbrzFreescale(u16 scale_factor) : TextureFilterBase(scale_factor)
state.texture_units[0].sampler = src_sampler.handle;
}
void XbrzFreescale::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) {
void XbrzFreescale::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = src_tex;
state.draw.draw_framebuffer = draw_fb_handle;
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_base.h"
@ -15,9 +14,8 @@ public:
static constexpr std::string_view NAME = "xBRZ freescale";
explicit XbrzFreescale(u16 scale_factor);
void Filter(GLuint src_tex, const Common::Rectangle<u32>& src_rect, GLuint dst_tex,
const Common::Rectangle<u32>& dst_rect, GLuint read_fb_handle,
GLuint draw_fb_handle) override;
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
OpenGLState state{};