mirror of
https://github.com/citra-emu/citra.git
synced 2024-12-18 13:40:04 +00:00
Merge pull request #6103 from GPUCode/tex-cache
Start abstracting the rasterizer cache from OpenGL
This commit is contained in:
commit
746609f35f
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -157,7 +157,7 @@ jobs:
|
||||
TRANSIFEX_API_TOKEN: ${{ secrets.TRANSIFEX_API_TOKEN }}
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, macos, android]
|
||||
needs: [build, android]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# Build directory
|
||||
[Bb]uild/
|
||||
doc-build/
|
||||
build-*/
|
||||
|
||||
# Generated source files
|
||||
src/common/scm_rev.cpp
|
||||
|
37
externals/glad/include/KHR/khrplatform.h
vendored
37
externals/glad/include/KHR/khrplatform.h
vendored
@ -90,12 +90,20 @@
|
||||
* int arg2) KHRONOS_APIATTRIBUTES;
|
||||
*/
|
||||
|
||||
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
|
||||
# define KHRONOS_STATIC 1
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------------------------------
|
||||
* Definition of KHRONOS_APICALL
|
||||
*-------------------------------------------------------------------------
|
||||
* This precedes the return type of the function in the function prototype.
|
||||
*/
|
||||
#if defined(_WIN32) && !defined(__SCITECH_SNAP__)
|
||||
#if defined(KHRONOS_STATIC)
|
||||
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
|
||||
* header compatible with static linking. */
|
||||
# define KHRONOS_APICALL
|
||||
#elif defined(_WIN32)
|
||||
# define KHRONOS_APICALL __declspec(dllimport)
|
||||
#elif defined (__SYMBIAN32__)
|
||||
# define KHRONOS_APICALL IMPORT_C
|
||||
@ -145,6 +153,20 @@ typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
/*
|
||||
* To support platform where unsigned long cannot be used interchangeably with
|
||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||
* unsigned long long or similar (this results in different C++ name mangling).
|
||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||
* platforms where the size of a pointer is larger than the size of long.
|
||||
*/
|
||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||
#define KHRONOS_USE_INTPTR_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
@ -227,14 +249,21 @@ typedef unsigned short int khronos_uint16_t;
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef _WIN64
|
||||
#ifdef KHRONOS_USE_INTPTR_T
|
||||
typedef intptr_t khronos_intptr_t;
|
||||
typedef uintptr_t khronos_uintptr_t;
|
||||
#elif defined(_WIN64)
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN64)
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
|
16652
externals/glad/include/glad/glad.h
vendored
16652
externals/glad/include/glad/glad.h
vendored
File diff suppressed because one or more lines are too long
9407
externals/glad/src/glad.c
vendored
9407
externals/glad/src/glad.c
vendored
File diff suppressed because one or more lines are too long
@ -147,14 +147,16 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
|
||||
SDL_SetMainReady();
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
if (Settings::values.use_gles) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
} else {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include <QOffscreenSurface>
|
||||
#include <QOpenGLContext>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLFunctions_3_3_Core>
|
||||
#include <QOpenGLFunctions_4_3_Core>
|
||||
#include <fmt/format.h>
|
||||
#include "citra_qt/bootmanager.h"
|
||||
#include "citra_qt/main.h"
|
||||
@ -143,7 +143,7 @@ void OpenGLWindow::Present() {
|
||||
VideoCore::g_renderer->TryPresent(100);
|
||||
}
|
||||
context->swapBuffers(this);
|
||||
auto f = context->versionFunctions<QOpenGLFunctions_3_3_Core>();
|
||||
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
|
||||
f->glFinish();
|
||||
QWindow::requestUpdate();
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QOpenGLFunctions_3_3_Core>
|
||||
#include <QOpenGLFunctions_4_3_Core>
|
||||
#include <QSysInfo>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QtGui>
|
||||
@ -916,12 +916,12 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
|
||||
Frontend::ScopeAcquireContext scope(*render_window);
|
||||
|
||||
const QString below_gl33_title = tr("OpenGL 3.3 Unsupported");
|
||||
const QString below_gl33_message = tr("Your GPU may not support OpenGL 3.3, or you do not "
|
||||
const QString below_gl43_title = tr("OpenGL 4.3 Unsupported");
|
||||
const QString below_gl43_message = tr("Your GPU may not support OpenGL 4.3, or you do not "
|
||||
"have the latest graphics driver.");
|
||||
|
||||
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_3_3_Core>()) {
|
||||
QMessageBox::critical(this, below_gl33_title, below_gl33_message);
|
||||
if (!QOpenGLContext::globalShareContext()->versionFunctions<QOpenGLFunctions_4_3_Core>()) {
|
||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -991,8 +991,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
|
||||
"proper drivers for your graphics card from the manufacturer's website."));
|
||||
break;
|
||||
|
||||
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL33:
|
||||
QMessageBox::critical(this, below_gl33_title, below_gl33_message);
|
||||
case Core::System::ResultStatus::ErrorVideoCore_ErrorBelowGL43:
|
||||
QMessageBox::critical(this, below_gl43_title, below_gl43_message);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -2441,7 +2441,7 @@ int main(int argc, char* argv[]) {
|
||||
QCoreApplication::setApplicationName(QStringLiteral("Citra"));
|
||||
|
||||
QSurfaceFormat format;
|
||||
format.setVersion(3, 3);
|
||||
format.setVersion(4, 3);
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
format.setSwapInterval(0);
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
|
@ -31,6 +31,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
#include <boost/serialization/access.hpp>
|
||||
|
||||
@ -60,6 +61,10 @@ public:
|
||||
return &x;
|
||||
}
|
||||
|
||||
const T* AsArray() const {
|
||||
return &x;
|
||||
}
|
||||
|
||||
constexpr Vec2() = default;
|
||||
constexpr Vec2(const T& x_, const T& y_) : x(x_), y(y_) {}
|
||||
|
||||
@ -123,6 +128,14 @@ public:
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const Vec2& other) const {
|
||||
return std::memcmp(AsArray(), other.AsArray(), sizeof(Vec2)) != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const Vec2& other) const {
|
||||
return std::memcmp(AsArray(), other.AsArray(), sizeof(Vec2)) == 0;
|
||||
}
|
||||
|
||||
// Only implemented for T=float
|
||||
[[nodiscard]] float Length() const;
|
||||
[[nodiscard]] float Normalize(); // returns the previous length, which is often useful
|
||||
@ -184,6 +197,8 @@ template <typename T, typename V>
|
||||
}
|
||||
|
||||
using Vec2f = Vec2<float>;
|
||||
using Vec2i = Vec2<int>;
|
||||
using Vec2u = Vec2<unsigned int>;
|
||||
|
||||
template <>
|
||||
inline float Vec2<float>::Length() const {
|
||||
@ -216,6 +231,10 @@ public:
|
||||
return &x;
|
||||
}
|
||||
|
||||
const T* AsArray() const {
|
||||
return &x;
|
||||
}
|
||||
|
||||
constexpr Vec3() = default;
|
||||
constexpr Vec3(const T& x_, const T& y_, const T& z_) : x(x_), y(y_), z(z_) {}
|
||||
|
||||
@ -280,6 +299,14 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const Vec3& other) const {
|
||||
return std::memcmp(AsArray(), other.AsArray(), sizeof(Vec3)) != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const Vec3& other) const {
|
||||
return std::memcmp(AsArray(), other.AsArray(), sizeof(Vec3)) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr T Length2() const {
|
||||
return x * x + y * y + z * z;
|
||||
}
|
||||
@ -412,6 +439,8 @@ inline float Vec3<float>::Normalize() {
|
||||
}
|
||||
|
||||
using Vec3f = Vec3<float>;
|
||||
using Vec3i = Vec3<int>;
|
||||
using Vec3u = Vec3<unsigned int>;
|
||||
|
||||
template <typename T>
|
||||
class Vec4 {
|
||||
@ -434,6 +463,10 @@ public:
|
||||
return &x;
|
||||
}
|
||||
|
||||
const T* AsArray() const {
|
||||
return &x;
|
||||
}
|
||||
|
||||
constexpr Vec4() = default;
|
||||
constexpr Vec4(const T& x_, const T& y_, const T& z_, const T& w_)
|
||||
: x(x_), y(y_), z(z_), w(w_) {}
|
||||
@ -503,6 +536,14 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator!=(const Vec4& other) const {
|
||||
return std::memcmp(AsArray(), other.AsArray(), sizeof(Vec4)) != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool operator==(const Vec4& other) const {
|
||||
return std::memcmp(AsArray(), other.AsArray(), sizeof(Vec4)) == 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr T Length2() const {
|
||||
return x * x + y * y + z * z + w * w;
|
||||
}
|
||||
@ -623,6 +664,8 @@ template <typename T, typename V>
|
||||
}
|
||||
|
||||
using Vec4f = Vec4<float>;
|
||||
using Vec4i = Vec4<int>;
|
||||
using Vec4u = Vec4<unsigned int>;
|
||||
|
||||
template <typename T>
|
||||
constexpr decltype(T{} * T{} + T{} * T{}) Dot(const Vec2<T>& a, const Vec2<T>& b) {
|
||||
|
@ -422,8 +422,8 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
|
||||
switch (result) {
|
||||
case VideoCore::ResultStatus::ErrorGenericDrivers:
|
||||
return ResultStatus::ErrorVideoCore_ErrorGenericDrivers;
|
||||
case VideoCore::ResultStatus::ErrorBelowGL33:
|
||||
return ResultStatus::ErrorVideoCore_ErrorBelowGL33;
|
||||
case VideoCore::ResultStatus::ErrorBelowGL43:
|
||||
return ResultStatus::ErrorVideoCore_ErrorBelowGL43;
|
||||
default:
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ public:
|
||||
ErrorVideoCore, ///< Error in the video core
|
||||
ErrorVideoCore_ErrorGenericDrivers, ///< Error in the video core due to the user having
|
||||
/// generic drivers installed
|
||||
ErrorVideoCore_ErrorBelowGL33, ///< Error in the video core due to the user not having
|
||||
/// OpenGL 3.3 or higher
|
||||
ErrorVideoCore_ErrorBelowGL43, ///< Error in the video core due to the user not having
|
||||
/// OpenGL 4.3 or higher
|
||||
ErrorSavestate, ///< Error saving or loading
|
||||
ShutdownRequested, ///< Emulated program requested a system shutdown
|
||||
ErrorUnknown ///< Any other error
|
||||
|
@ -23,12 +23,23 @@ 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
|
||||
rasterizer_cache/rasterizer_cache.h
|
||||
rasterizer_cache/rasterizer_cache_types.h
|
||||
rasterizer_cache/rasterizer_cache_utils.cpp
|
||||
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
|
||||
renderer_opengl/gl_rasterizer.h
|
||||
renderer_opengl/gl_rasterizer_cache.cpp
|
||||
renderer_opengl/gl_rasterizer_cache.h
|
||||
renderer_opengl/gl_resource_manager.cpp
|
||||
renderer_opengl/gl_resource_manager.h
|
||||
renderer_opengl/gl_shader_decompiler.cpp
|
||||
@ -45,8 +56,6 @@ add_library(video_core STATIC
|
||||
renderer_opengl/gl_state.h
|
||||
renderer_opengl/gl_stream_buffer.cpp
|
||||
renderer_opengl/gl_stream_buffer.h
|
||||
renderer_opengl/gl_surface_params.cpp
|
||||
renderer_opengl/gl_surface_params.h
|
||||
renderer_opengl/gl_vars.cpp
|
||||
renderer_opengl/gl_vars.h
|
||||
renderer_opengl/pica_to_gl.h
|
||||
|
475
src/video_core/rasterizer_cache/cached_surface.cpp
Normal file
475
src/video_core/rasterizer_cache/cached_surface.cpp
Normal file
@ -0,0 +1,475 @@
|
||||
// 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/scope_exit.h"
|
||||
#include "common/texture.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/gl_state.h"
|
||||
#include "video_core/renderer_opengl/texture_downloader_es.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filterer.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.
|
||||
*/
|
||||
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
|
137
src/video_core/rasterizer_cache/cached_surface.h
Normal file
137
src/video_core/rasterizer_cache/cached_surface.h
Normal file
@ -0,0 +1,137 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <list>
|
||||
#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(SurfaceParams params, RasterizerCacheOpenGL& owner, TextureRuntime& runtime)
|
||||
: SurfaceParams(params), 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
|
171
src/video_core/rasterizer_cache/morton_swizzle.h
Normal file
171
src/video_core/rasterizer_cache/morton_swizzle.h
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include "common/alignment.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/utils.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
|
||||
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
|
||||
constexpr u32 aligned_bytes_per_pixel = GetBytesPerPixel(format);
|
||||
for (u32 y = 0; y < 8; ++y) {
|
||||
for (u32 x = 0; x < 8; ++x) {
|
||||
u8* tile_ptr = tile_buffer + VideoCore::MortonInterleave(x, y) * bytes_per_pixel;
|
||||
u8* gl_ptr = gl_buffer + ((7 - y) * stride + x) * aligned_bytes_per_pixel;
|
||||
if constexpr (morton_to_gl) {
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
gl_ptr[0] = tile_ptr[3];
|
||||
std::memcpy(gl_ptr + 1, tile_ptr, 3);
|
||||
} else if (format == PixelFormat::RGBA8 && GLES) {
|
||||
// because GLES does not have ABGR format
|
||||
// so we will do byteswapping here
|
||||
gl_ptr[0] = tile_ptr[3];
|
||||
gl_ptr[1] = tile_ptr[2];
|
||||
gl_ptr[2] = tile_ptr[1];
|
||||
gl_ptr[3] = tile_ptr[0];
|
||||
} else if (format == PixelFormat::RGB8 && GLES) {
|
||||
gl_ptr[0] = tile_ptr[2];
|
||||
gl_ptr[1] = tile_ptr[1];
|
||||
gl_ptr[2] = tile_ptr[0];
|
||||
} else {
|
||||
std::memcpy(gl_ptr, tile_ptr, bytes_per_pixel);
|
||||
}
|
||||
} else {
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
std::memcpy(tile_ptr, gl_ptr + 1, 3);
|
||||
tile_ptr[3] = gl_ptr[0];
|
||||
} else if (format == PixelFormat::RGBA8 && GLES) {
|
||||
// because GLES does not have ABGR format
|
||||
// so we will do byteswapping here
|
||||
tile_ptr[0] = gl_ptr[3];
|
||||
tile_ptr[1] = gl_ptr[2];
|
||||
tile_ptr[2] = gl_ptr[1];
|
||||
tile_ptr[3] = gl_ptr[0];
|
||||
} else if (format == PixelFormat::RGB8 && GLES) {
|
||||
tile_ptr[0] = gl_ptr[2];
|
||||
tile_ptr[1] = gl_ptr[1];
|
||||
tile_ptr[2] = gl_ptr[0];
|
||||
} else {
|
||||
std::memcpy(tile_ptr, gl_ptr, bytes_per_pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool morton_to_gl, PixelFormat format>
|
||||
static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, PAddr base, PAddr start, PAddr end) {
|
||||
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
|
||||
constexpr u32 tile_size = bytes_per_pixel * 64;
|
||||
|
||||
constexpr u32 aligned_bytes_per_pixel = GetBytesPerPixel(format);
|
||||
static_assert(aligned_bytes_per_pixel >= bytes_per_pixel, "");
|
||||
gl_buffer += aligned_bytes_per_pixel - bytes_per_pixel;
|
||||
|
||||
const PAddr aligned_down_start = base + Common::AlignDown(start - base, tile_size);
|
||||
const PAddr aligned_start = base + Common::AlignUp(start - base, tile_size);
|
||||
const PAddr aligned_end = base + Common::AlignDown(end - base, tile_size);
|
||||
|
||||
ASSERT(!morton_to_gl || (aligned_start == start && aligned_end == end));
|
||||
|
||||
const u32 begin_pixel_index = (aligned_down_start - base) / bytes_per_pixel;
|
||||
u32 x = (begin_pixel_index % (stride * 8)) / 8;
|
||||
u32 y = (begin_pixel_index / (stride * 8)) * 8;
|
||||
|
||||
gl_buffer += ((height - 8 - y) * stride + x) * aligned_bytes_per_pixel;
|
||||
|
||||
auto glbuf_next_tile = [&] {
|
||||
x = (x + 8) % stride;
|
||||
gl_buffer += 8 * aligned_bytes_per_pixel;
|
||||
if (!x) {
|
||||
y += 8;
|
||||
gl_buffer -= stride * 9 * aligned_bytes_per_pixel;
|
||||
}
|
||||
};
|
||||
|
||||
u8* tile_buffer = VideoCore::g_memory->GetPhysicalPointer(start);
|
||||
|
||||
if (start < aligned_start && !morton_to_gl) {
|
||||
std::array<u8, tile_size> tmp_buf;
|
||||
MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer);
|
||||
std::memcpy(tile_buffer, &tmp_buf[start - aligned_down_start],
|
||||
std::min(aligned_start, end) - start);
|
||||
|
||||
tile_buffer += aligned_start - start;
|
||||
glbuf_next_tile();
|
||||
}
|
||||
|
||||
const u8* const buffer_end = tile_buffer + aligned_end - aligned_start;
|
||||
PAddr current_paddr = aligned_start;
|
||||
while (tile_buffer < buffer_end) {
|
||||
// Pokemon Super Mystery Dungeon will try to use textures that go beyond
|
||||
// the end address of VRAM. Stop reading if reaches invalid address
|
||||
if (!VideoCore::g_memory->IsValidPhysicalAddress(current_paddr) ||
|
||||
!VideoCore::g_memory->IsValidPhysicalAddress(current_paddr + tile_size)) {
|
||||
LOG_ERROR(Render_OpenGL, "Out of bound texture");
|
||||
break;
|
||||
}
|
||||
MortonCopyTile<morton_to_gl, format>(stride, tile_buffer, gl_buffer);
|
||||
tile_buffer += tile_size;
|
||||
current_paddr += tile_size;
|
||||
glbuf_next_tile();
|
||||
}
|
||||
|
||||
if (end > std::max(aligned_start, aligned_end) && !morton_to_gl) {
|
||||
std::array<u8, tile_size> tmp_buf;
|
||||
MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer);
|
||||
std::memcpy(tile_buffer, &tmp_buf[0], end - aligned_end);
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> morton_to_gl_fns = {
|
||||
MortonCopy<true, PixelFormat::RGBA8>, // 0
|
||||
MortonCopy<true, PixelFormat::RGB8>, // 1
|
||||
MortonCopy<true, PixelFormat::RGB5A1>, // 2
|
||||
MortonCopy<true, PixelFormat::RGB565>, // 3
|
||||
MortonCopy<true, PixelFormat::RGBA4>, // 4
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr, // 5 - 13
|
||||
MortonCopy<true, PixelFormat::D16>, // 14
|
||||
nullptr, // 15
|
||||
MortonCopy<true, PixelFormat::D24>, // 16
|
||||
MortonCopy<true, PixelFormat::D24S8> // 17
|
||||
};
|
||||
|
||||
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl_to_morton_fns = {
|
||||
MortonCopy<false, PixelFormat::RGBA8>, // 0
|
||||
MortonCopy<false, PixelFormat::RGB8>, // 1
|
||||
MortonCopy<false, PixelFormat::RGB5A1>, // 2
|
||||
MortonCopy<false, PixelFormat::RGB565>, // 3
|
||||
MortonCopy<false, PixelFormat::RGBA4>, // 4
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr, // 5 - 13
|
||||
MortonCopy<false, PixelFormat::D16>, // 14
|
||||
nullptr, // 15
|
||||
MortonCopy<false, PixelFormat::D24>, // 16
|
||||
MortonCopy<false, PixelFormat::D24S8> // 17
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
196
src/video_core/rasterizer_cache/pixel_format.h
Normal file
196
src/video_core/rasterizer_cache/pixel_format.h
Normal file
@ -0,0 +1,196 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
constexpr u32 PIXEL_FORMAT_COUNT = 18;
|
||||
|
||||
enum class PixelFormat : u8 {
|
||||
// First 5 formats are shared between textures and color buffers
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
// Texture-only formats
|
||||
IA8 = 5,
|
||||
RG8 = 6,
|
||||
I8 = 7,
|
||||
A8 = 8,
|
||||
IA4 = 9,
|
||||
I4 = 10,
|
||||
A4 = 11,
|
||||
ETC1 = 12,
|
||||
ETC1A4 = 13,
|
||||
// Depth buffer-only formats
|
||||
D16 = 14,
|
||||
D24 = 16,
|
||||
D24S8 = 17,
|
||||
Invalid = 255,
|
||||
};
|
||||
|
||||
enum class SurfaceType {
|
||||
Color = 0,
|
||||
Texture = 1,
|
||||
Depth = 2,
|
||||
DepthStencil = 3,
|
||||
Fill = 4,
|
||||
Invalid = 5
|
||||
};
|
||||
|
||||
constexpr std::string_view PixelFormatAsString(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
return "RGBA8";
|
||||
case PixelFormat::RGB8:
|
||||
return "RGB8";
|
||||
case PixelFormat::RGB5A1:
|
||||
return "RGB5A1";
|
||||
case PixelFormat::RGB565:
|
||||
return "RGB565";
|
||||
case PixelFormat::RGBA4:
|
||||
return "RGBA4";
|
||||
case PixelFormat::IA8:
|
||||
return "IA8";
|
||||
case PixelFormat::RG8:
|
||||
return "RG8";
|
||||
case PixelFormat::I8:
|
||||
return "I8";
|
||||
case PixelFormat::A8:
|
||||
return "A8";
|
||||
case PixelFormat::IA4:
|
||||
return "IA4";
|
||||
case PixelFormat::I4:
|
||||
return "I4";
|
||||
case PixelFormat::A4:
|
||||
return "A4";
|
||||
case PixelFormat::ETC1:
|
||||
return "ETC1";
|
||||
case PixelFormat::ETC1A4:
|
||||
return "ETC1A4";
|
||||
case PixelFormat::D16:
|
||||
return "D16";
|
||||
case PixelFormat::D24:
|
||||
return "D24";
|
||||
case PixelFormat::D24S8:
|
||||
return "D24S8";
|
||||
default:
|
||||
return "NotReal";
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
constexpr 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;
|
||||
}
|
||||
|
||||
constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
|
||||
const u32 format_index = static_cast<u32>(format);
|
||||
switch (format) {
|
||||
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return PixelFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return PixelFormat::RGB5A1;
|
||||
default:
|
||||
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
|
||||
const u32 format_index = static_cast<u32>(pixel_format);
|
||||
if (format_index < 5) {
|
||||
return SurfaceType::Color;
|
||||
}
|
||||
|
||||
if (format_index < 14) {
|
||||
return SurfaceType::Texture;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
|
||||
return SurfaceType::Depth;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D24S8) {
|
||||
return SurfaceType::DepthStencil;
|
||||
}
|
||||
|
||||
return SurfaceType::Invalid;
|
||||
}
|
||||
|
||||
constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
|
||||
SurfaceType source_type = GetFormatType(source_format);
|
||||
SurfaceType dest_type = GetFormatType(dest_format);
|
||||
|
||||
if ((source_type == SurfaceType::Color || source_type == SurfaceType::Texture) &&
|
||||
(dest_type == SurfaceType::Color || dest_type == SurfaceType::Texture)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source_type == SurfaceType::Depth && dest_type == SurfaceType::Depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source_type == SurfaceType::DepthStencil && dest_type == SurfaceType::DepthStencil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr u32 GetFormatBpp(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
case PixelFormat::D24S8:
|
||||
return 32;
|
||||
case PixelFormat::RGB8:
|
||||
case PixelFormat::D24:
|
||||
return 24;
|
||||
case PixelFormat::RGB5A1:
|
||||
case PixelFormat::RGB565:
|
||||
case PixelFormat::RGBA4:
|
||||
case PixelFormat::IA8:
|
||||
case PixelFormat::RG8:
|
||||
case PixelFormat::D16:
|
||||
return 16;
|
||||
case PixelFormat::I8:
|
||||
case PixelFormat::A8:
|
||||
case PixelFormat::IA4:
|
||||
case PixelFormat::ETC1A4:
|
||||
return 8;
|
||||
case PixelFormat::I4:
|
||||
case PixelFormat::A4:
|
||||
case PixelFormat::ETC1:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return GetFormatBpp(format) / 8;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
1104
src/video_core/rasterizer_cache/rasterizer_cache.cpp
Normal file
1104
src/video_core/rasterizer_cache/rasterizer_cache.cpp
Normal file
File diff suppressed because it is too large
Load Diff
131
src/video_core/rasterizer_cache/rasterizer_cache.h
Normal file
131
src/video_core/rasterizer_cache/rasterizer_cache.h
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <unordered_map>
|
||||
#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/texture/texture_decode.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
enum class ScaleMatch {
|
||||
Exact, // only accept same res scale
|
||||
Upscale, // only allow higher scale than params
|
||||
Ignore // accept every scaled res
|
||||
};
|
||||
|
||||
class TextureDownloaderES;
|
||||
class TextureFilterer;
|
||||
class FormatReinterpreterOpenGL;
|
||||
|
||||
class RasterizerCacheOpenGL : NonCopyable {
|
||||
public:
|
||||
RasterizerCacheOpenGL();
|
||||
~RasterizerCacheOpenGL();
|
||||
|
||||
/// Blit one surface's texture to another
|
||||
bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect,
|
||||
const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect);
|
||||
|
||||
/// Copy one surface's region to another
|
||||
void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
SurfaceInterval copy_interval);
|
||||
|
||||
/// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
|
||||
Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
/// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
|
||||
/// 3DS memory to OpenGL and caches it (if not already cached)
|
||||
SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
/// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
|
||||
Surface GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
|
||||
|
||||
/// Get a texture cube based on the texture configuration
|
||||
const CachedTextureCube& GetTextureCube(const TextureCubeConfig& config);
|
||||
|
||||
/// Get the color and depth surfaces based on the framebuffer configuration
|
||||
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
const Common::Rectangle<s32>& viewport_rect);
|
||||
|
||||
/// Get a surface that matches the fill config
|
||||
Surface GetFillSurface(const GPU::Regs::MemoryFillConfig& config);
|
||||
|
||||
/// Get a surface that matches a "texture copy" display transfer config
|
||||
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
||||
|
||||
/// Write any cached resources overlapping the region back to memory (if dirty)
|
||||
void FlushRegion(PAddr addr, u32 size, Surface flush_surface = nullptr);
|
||||
|
||||
/// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
|
||||
void InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner);
|
||||
|
||||
/// Flush all cached resources tracked by this cache manager
|
||||
void FlushAll();
|
||||
|
||||
/// Clear all cached resources tracked by this cache manager
|
||||
void ClearAll(bool flush);
|
||||
|
||||
// Textures from destroyed surfaces are stored here to be recyled to reduce allocation overhead
|
||||
// in the driver
|
||||
// this must be placed above the surface_cache to ensure all cached surfaces are destroyed
|
||||
// before destroying the recycler
|
||||
std::unordered_multimap<HostTextureTag, OGLTexture> host_texture_recycler;
|
||||
|
||||
private:
|
||||
void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
|
||||
|
||||
/// Update surface's texture for given region when necessary
|
||||
void ValidateSurface(const Surface& surface, PAddr addr, u32 size);
|
||||
|
||||
// Returns false if there is a surface in the cache at the interval with the same bit-width,
|
||||
bool NoUnimplementedReinterpretations(const OpenGL::Surface& surface,
|
||||
OpenGL::SurfaceParams& params,
|
||||
const OpenGL::SurfaceInterval& interval);
|
||||
|
||||
// Return true if a surface with an invalid pixel format exists at the interval
|
||||
bool IntervalHasInvalidPixelFormat(SurfaceParams& params, const SurfaceInterval& interval);
|
||||
|
||||
// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
|
||||
bool ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
|
||||
const SurfaceInterval& interval);
|
||||
|
||||
/// Create a new surface
|
||||
Surface CreateSurface(const SurfaceParams& params);
|
||||
|
||||
/// Register surface into the cache
|
||||
void RegisterSurface(const Surface& surface);
|
||||
|
||||
/// Remove surface from the cache
|
||||
void UnregisterSurface(const Surface& surface);
|
||||
|
||||
/// 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;
|
||||
|
||||
u16 resolution_scale_factor;
|
||||
|
||||
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
public:
|
||||
OGLTexture AllocateSurfaceTexture(const FormatTuple& format_tuple, u32 width, u32 height);
|
||||
|
||||
std::unique_ptr<TextureFilterer> texture_filterer;
|
||||
std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter;
|
||||
std::unique_ptr<TextureDownloaderES> texture_downloader_es;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
38
src/video_core/rasterizer_cache/rasterizer_cache_types.h
Normal file
38
src/video_core/rasterizer_cache/rasterizer_cache_types.h
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
|
||||
// Declare rasterizer interval types
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
using SurfaceSet = std::set<Surface>;
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
using SurfaceMap =
|
||||
boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
using SurfaceCache =
|
||||
boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
|
||||
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
|
||||
"Incorrect interval types");
|
||||
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
using PageMap = boost::icl::interval_map<u32, int>;
|
||||
|
||||
} // namespace OpenGL
|
56
src/video_core/rasterizer_cache/rasterizer_cache_utils.cpp
Normal file
56
src/video_core/rasterizer_cache/rasterizer_cache_utils.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <glad/glad.h>
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
|
||||
static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
|
||||
{},
|
||||
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
|
||||
}};
|
||||
|
||||
static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
|
||||
{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
||||
}};
|
||||
|
||||
// Same as above, with minor changes for OpenGL ES. Replaced
|
||||
// GL_UNSIGNED_INT_8_8_8_8 with GL_UNSIGNED_BYTE and
|
||||
// GL_BGR with GL_RGB
|
||||
static constexpr std::array<FormatTuple, 5> fb_format_tuples_oes = {{
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, // RGB8
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
||||
}};
|
||||
|
||||
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
|
||||
const SurfaceType type = GetFormatType(pixel_format);
|
||||
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
|
||||
|
||||
if (type == SurfaceType::Color) {
|
||||
ASSERT(format_index < fb_format_tuples.size());
|
||||
return (GLES ? fb_format_tuples_oes : fb_format_tuples)[format_index];
|
||||
} else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
|
||||
const std::size_t tuple_idx = format_index - 14;
|
||||
ASSERT(tuple_idx < depth_format_tuples.size());
|
||||
return depth_format_tuples[tuple_idx];
|
||||
}
|
||||
|
||||
return tex_tuple;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
73
src/video_core/rasterizer_cache/rasterizer_cache_utils.h
Normal file
73
src/video_core/rasterizer_cache/rasterizer_cache_utils.h
Normal file
@ -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 <functional>
|
||||
#include "common/hash.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct FormatTuple {
|
||||
int internal_format;
|
||||
u32 format;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
const FormatTuple& GetFormatTuple(PixelFormat pixel_format);
|
||||
|
||||
struct HostTextureTag {
|
||||
FormatTuple format_tuple{};
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
|
||||
bool operator==(const HostTextureTag& rhs) const noexcept {
|
||||
return std::memcmp(this, &rhs, sizeof(HostTextureTag)) == 0;
|
||||
};
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeHash64(this, sizeof(HostTextureTag));
|
||||
}
|
||||
};
|
||||
|
||||
struct TextureCubeConfig {
|
||||
PAddr px;
|
||||
PAddr nx;
|
||||
PAddr py;
|
||||
PAddr ny;
|
||||
PAddr pz;
|
||||
PAddr nz;
|
||||
u32 width;
|
||||
Pica::TexturingRegs::TextureFormat format;
|
||||
|
||||
bool operator==(const TextureCubeConfig& rhs) const {
|
||||
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) == 0;
|
||||
}
|
||||
|
||||
bool operator!=(const TextureCubeConfig& rhs) const {
|
||||
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) != 0;
|
||||
}
|
||||
|
||||
const u64 Hash() const {
|
||||
return Common::ComputeHash64(this, sizeof(TextureCubeConfig));
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<OpenGL::HostTextureTag> {
|
||||
std::size_t operator()(const OpenGL::HostTextureTag& tag) const noexcept {
|
||||
return tag.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::TextureCubeConfig> {
|
||||
std::size_t operator()(const OpenGL::TextureCubeConfig& config) const noexcept {
|
||||
return config.Hash();
|
||||
}
|
||||
};
|
||||
} // namespace std
|
@ -1,10 +1,10 @@
|
||||
// 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/alignment.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_surface_params.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
@ -12,6 +12,7 @@ SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
|
||||
SurfaceParams params = *this;
|
||||
const u32 tiled_size = is_tiled ? 8 : 1;
|
||||
const u32 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
|
||||
|
||||
PAddr aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
|
||||
PAddr aligned_end =
|
||||
@ -24,17 +25,19 @@ SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
|
||||
// 1 row
|
||||
ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
|
||||
const u32 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
|
||||
|
||||
aligned_start =
|
||||
addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment);
|
||||
aligned_end =
|
||||
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
|
||||
|
||||
params.addr = aligned_start;
|
||||
params.width = PixelsInBytes(aligned_end - aligned_start) / tiled_size;
|
||||
params.stride = params.width;
|
||||
params.height = tiled_size;
|
||||
}
|
||||
params.UpdateParams();
|
||||
|
||||
params.UpdateParams();
|
||||
return params;
|
||||
}
|
||||
|
||||
@ -158,6 +161,7 @@ bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
|
||||
end < texcopy_params.end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (texcopy_params.width != texcopy_params.stride) {
|
||||
const u32 tile_stride = BytesInPixels(stride * (is_tiled ? 8 : 1));
|
||||
return (texcopy_params.addr - addr) % BytesInPixels(is_tiled ? 64 : 1) == 0 &&
|
||||
@ -165,6 +169,7 @@ bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
|
||||
(texcopy_params.height == 1 || texcopy_params.stride == tile_stride) &&
|
||||
((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride;
|
||||
}
|
||||
|
||||
return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval();
|
||||
}
|
||||
|
91
src/video_core/rasterizer_cache/surface_params.h
Normal file
91
src/video_core/rasterizer_cache/surface_params.h
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache_types.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class SurfaceParams {
|
||||
public:
|
||||
// Surface match traits
|
||||
bool ExactMatch(const SurfaceParams& other_surface) const;
|
||||
bool CanSubRect(const SurfaceParams& sub_surface) const;
|
||||
bool CanExpand(const SurfaceParams& expanded_surface) const;
|
||||
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
|
||||
|
||||
Common::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
|
||||
Common::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
|
||||
|
||||
// Returns the outer rectangle containing "interval"
|
||||
SurfaceParams FromInterval(SurfaceInterval interval) const;
|
||||
SurfaceInterval GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const;
|
||||
|
||||
// Returns the region of the biggest valid rectange within interval
|
||||
SurfaceInterval GetCopyableInterval(const Surface& src_surface) const;
|
||||
|
||||
/// Updates remaining members from the already set addr, width, height and pixel_format
|
||||
void UpdateParams() {
|
||||
if (stride == 0) {
|
||||
stride = width;
|
||||
}
|
||||
|
||||
type = GetFormatType(pixel_format);
|
||||
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
|
||||
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
|
||||
end = addr + size;
|
||||
}
|
||||
|
||||
SurfaceInterval GetInterval() const {
|
||||
return SurfaceInterval(addr, end);
|
||||
}
|
||||
|
||||
u32 GetFormatBpp() const {
|
||||
return OpenGL::GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
u32 GetScaledWidth() const {
|
||||
return width * res_scale;
|
||||
}
|
||||
|
||||
u32 GetScaledHeight() const {
|
||||
return height * res_scale;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetRect() const {
|
||||
return {0, height, width, 0};
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetScaledRect() const {
|
||||
return {0, GetScaledHeight(), GetScaledWidth(), 0};
|
||||
}
|
||||
|
||||
u32 PixelsInBytes(u32 size) const {
|
||||
return size * 8 / GetFormatBpp();
|
||||
}
|
||||
|
||||
u32 BytesInPixels(u32 pixels) const {
|
||||
return pixels * GetFormatBpp() / 8;
|
||||
}
|
||||
|
||||
public:
|
||||
PAddr addr = 0;
|
||||
PAddr end = 0;
|
||||
u32 size = 0;
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 stride = 0;
|
||||
u16 res_scale = 1;
|
||||
|
||||
bool is_tiled = false;
|
||||
PixelFormat pixel_format = PixelFormat::Invalid;
|
||||
SurfaceType type = SurfaceType::Invalid;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
195
src/video_core/rasterizer_cache/texture_runtime.cpp
Normal file
195
src/video_core/rasterizer_cache/texture_runtime.cpp
Normal file
@ -0,0 +1,195 @@
|
||||
// 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
|
69
src/video_core/rasterizer_cache/texture_runtime.h
Normal file
69
src/video_core/rasterizer_cache/texture_runtime.h
Normal file
@ -0,0 +1,69 @@
|
||||
// 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
|
@ -3,13 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_pipeline.h"
|
||||
|
@ -3,9 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
|
@ -1,19 +1,13 @@
|
||||
// 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/renderer_opengl/gl_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 {
|
||||
|
||||
using PixelFormat = SurfaceParams::PixelFormat;
|
||||
|
||||
class RGBA4toRGB5A1 final : public FormatReinterpreterBase {
|
||||
public:
|
||||
RGBA4toRGB5A1() {
|
||||
@ -66,15 +60,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),
|
||||
@ -82,8 +79,8 @@ 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_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
dst_tex.handle, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
|
||||
@ -99,127 +96,6 @@ private:
|
||||
OGLVertexArray vao;
|
||||
};
|
||||
|
||||
class PixelBufferD24S8toABGR final : public FormatReinterpreterBase {
|
||||
public:
|
||||
PixelBufferD24S8toABGR() {
|
||||
attributeless_vao.Create();
|
||||
d24s8_abgr_buffer.Create();
|
||||
d24s8_abgr_buffer_size = 0;
|
||||
|
||||
constexpr std::string_view vs_source = R"(
|
||||
const vec2 vertices[4] = vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0),
|
||||
vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
void main() {
|
||||
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
std::string fs_source = GLES ? fragment_shader_precision_OES : "";
|
||||
fs_source += R"(
|
||||
uniform samplerBuffer tbo;
|
||||
uniform vec2 tbo_size;
|
||||
uniform vec4 viewport;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
vec2 tbo_coord = (gl_FragCoord.xy - viewport.xy) * tbo_size / viewport.zw;
|
||||
int tbo_offset = int(tbo_coord.y) * int(tbo_size.x) + int(tbo_coord.x);
|
||||
color = texelFetch(tbo, tbo_offset).rabg;
|
||||
}
|
||||
)";
|
||||
d24s8_abgr_shader.Create(vs_source.data(), fs_source.c_str());
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
GLuint old_program = state.draw.shader_program;
|
||||
state.draw.shader_program = d24s8_abgr_shader.handle;
|
||||
state.Apply();
|
||||
|
||||
GLint tbo_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo");
|
||||
ASSERT(tbo_u_id != -1);
|
||||
glUniform1i(tbo_u_id, 0);
|
||||
|
||||
state.draw.shader_program = old_program;
|
||||
state.Apply();
|
||||
|
||||
d24s8_abgr_tbo_size_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "tbo_size");
|
||||
ASSERT(d24s8_abgr_tbo_size_u_id != -1);
|
||||
d24s8_abgr_viewport_u_id = glGetUniformLocation(d24s8_abgr_shader.handle, "viewport");
|
||||
ASSERT(d24s8_abgr_viewport_u_id != -1);
|
||||
}
|
||||
|
||||
~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 {
|
||||
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.Apply();
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer.handle);
|
||||
|
||||
GLsizeiptr target_pbo_size =
|
||||
static_cast<GLsizeiptr>(src_rect.GetWidth()) * src_rect.GetHeight() * 4;
|
||||
if (target_pbo_size > d24s8_abgr_buffer_size) {
|
||||
d24s8_abgr_buffer_size = target_pbo_size * 2;
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, d24s8_abgr_buffer_size, nullptr, GL_STREAM_COPY);
|
||||
}
|
||||
|
||||
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);
|
||||
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,
|
||||
GL_UNSIGNED_INT_24_8, 0);
|
||||
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
// PBO now contains src_tex in RABG format
|
||||
state.draw.shader_program = d24s8_abgr_shader.handle;
|
||||
state.draw.vertex_array = attributeless_vao.handle;
|
||||
state.viewport.x = static_cast<GLint>(dst_rect.left);
|
||||
state.viewport.y = static_cast<GLint>(dst_rect.bottom);
|
||||
state.viewport.width = static_cast<GLsizei>(dst_rect.GetWidth());
|
||||
state.viewport.height = static_cast<GLsizei>(dst_rect.GetHeight());
|
||||
state.Apply();
|
||||
|
||||
OGLTexture tbo;
|
||||
tbo.Create();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_BUFFER, tbo.handle);
|
||||
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA8, d24s8_abgr_buffer.handle);
|
||||
|
||||
glUniform2f(d24s8_abgr_tbo_size_u_id, static_cast<GLfloat>(src_rect.GetWidth()),
|
||||
static_cast<GLfloat>(src_rect.GetHeight()));
|
||||
glUniform4f(d24s8_abgr_viewport_u_id, static_cast<GLfloat>(state.viewport.x),
|
||||
static_cast<GLfloat>(state.viewport.y),
|
||||
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);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
glBindTexture(GL_TEXTURE_BUFFER, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
OGLVertexArray attributeless_vao;
|
||||
OGLBuffer d24s8_abgr_buffer;
|
||||
GLsizeiptr d24s8_abgr_buffer_size;
|
||||
OGLProgram d24s8_abgr_shader;
|
||||
GLint d24s8_abgr_tbo_size_u_id;
|
||||
GLint d24s8_abgr_viewport_u_id;
|
||||
};
|
||||
|
||||
class ShaderD24S8toRGBA8 final : public FormatReinterpreterBase {
|
||||
public:
|
||||
ShaderD24S8toRGBA8() {
|
||||
@ -294,19 +170,23 @@ 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) {
|
||||
@ -322,7 +202,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),
|
||||
@ -332,14 +212,14 @@ 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_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
dst_tex.handle, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
|
||||
@ -365,32 +245,21 @@ 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>());
|
||||
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");
|
||||
}
|
||||
reinterpreters.emplace(PixelFormatPair{PixelFormat::RGB5A1, PixelFormat::RGBA4},
|
||||
std::make_unique<RGBA4toRGB5A1>());
|
||||
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));
|
||||
};
|
||||
|
||||
Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
|
||||
LOG_INFO(Render_OpenGL, "Using shader for D24S8 to RGBA8 reinterpretation");
|
||||
|
||||
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,47 @@
|
||||
// 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"
|
||||
#include "video_core/renderer_opengl/gl_surface_params.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class RasterizerCacheOpenGL;
|
||||
|
||||
struct PixelFormatPair {
|
||||
const SurfaceParams::PixelFormat dst_format, src_format;
|
||||
struct less {
|
||||
using is_transparent = void;
|
||||
constexpr bool operator()(OpenGL::PixelFormatPair lhs, OpenGL::PixelFormatPair rhs) const {
|
||||
return std::tie(lhs.dst_format, lhs.src_format) <
|
||||
std::tie(rhs.dst_format, rhs.src_format);
|
||||
}
|
||||
constexpr bool operator()(OpenGL::SurfaceParams::PixelFormat lhs,
|
||||
OpenGL::PixelFormatPair rhs) const {
|
||||
return lhs < rhs.dst_format;
|
||||
}
|
||||
constexpr bool operator()(OpenGL::PixelFormatPair lhs,
|
||||
OpenGL::SurfaceParams::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;
|
||||
OGLFramebuffer 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;
|
||||
|
||||
std::pair<ReinterpreterMap::iterator, ReinterpreterMap::iterator> GetPossibleReinterpretations(
|
||||
SurfaceParams::PixelFormat dst_format);
|
||||
const ReinterpreterList& GetPossibleReinterpretations(PixelFormat dst_format);
|
||||
|
||||
private:
|
||||
ReinterpreterMap reinterpreters;
|
||||
std::array<ReinterpreterList, PIXEL_FORMAT_COUNT> reinterpreters;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
@ -1,25 +1,15 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <glad/glad.h>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_rasterizer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
@ -29,9 +19,6 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using PixelFormat = SurfaceParams::PixelFormat;
|
||||
using SurfaceType = SurfaceParams::SurfaceType;
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(255, 128, 0));
|
||||
MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(192, 128, 128));
|
||||
MICROPROFILE_DEFINE(OpenGL_GS, "OpenGL", "Geometry Shader Setup", MP_RGB(128, 192, 128));
|
||||
@ -39,33 +26,19 @@ MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
|
||||
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
|
||||
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
|
||||
|
||||
static bool IsVendorAmd() {
|
||||
const std::string_view gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
return gpu_vendor == "ATI Technologies Inc." || gpu_vendor == "Advanced Micro Devices, Inc.";
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
static bool IsVendorIntel() {
|
||||
std::string gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
return gpu_vendor == "Intel Inc.";
|
||||
}
|
||||
#endif
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Frontend::EmuWindow& emu_window)
|
||||
: is_amd(IsVendorAmd()), vertex_buffer(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE, is_amd),
|
||||
uniform_buffer(GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE, false),
|
||||
index_buffer(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE, false),
|
||||
texture_buffer(GL_TEXTURE_BUFFER, TEXTURE_BUFFER_SIZE, false),
|
||||
texture_lf_buffer(GL_TEXTURE_BUFFER, TEXTURE_BUFFER_SIZE, false) {
|
||||
|
||||
allow_shadow = GLES || (GLAD_GL_ARB_shader_image_load_store && GLAD_GL_ARB_shader_image_size &&
|
||||
GLAD_GL_ARB_framebuffer_no_attachments);
|
||||
if (!allow_shadow) {
|
||||
LOG_WARNING(Render_OpenGL,
|
||||
"Shadow might not be able to render because of unsupported OpenGL extensions.");
|
||||
}
|
||||
|
||||
if (!GLAD_GL_ARB_copy_image && !GLES) {
|
||||
LOG_WARNING(Render_OpenGL,
|
||||
"ARB_copy_image not supported. Some games might produce artifacts.");
|
||||
}
|
||||
: vertex_buffer(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE),
|
||||
uniform_buffer(GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE),
|
||||
index_buffer(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE),
|
||||
texture_buffer(GL_TEXTURE_BUFFER, TEXTURE_BUFFER_SIZE),
|
||||
texture_lf_buffer(GL_TEXTURE_BUFFER, TEXTURE_BUFFER_SIZE) {
|
||||
|
||||
// Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
|
||||
state.clip_distance[0] = true;
|
||||
@ -171,22 +144,19 @@ RasterizerOpenGL::RasterizerOpenGL(Frontend::EmuWindow& emu_window)
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (IsVendorIntel()) {
|
||||
shader_program_manager = std::make_unique<ShaderProgramManager>(
|
||||
emu_window,
|
||||
VideoCore::g_separable_shader_enabled ? GLAD_GL_ARB_separate_shader_objects : false,
|
||||
is_amd);
|
||||
shader_program_manager = std::make_unique<ShaderProgramManager>(emu_window, false);
|
||||
} else {
|
||||
shader_program_manager = std::make_unique<ShaderProgramManager>(
|
||||
emu_window, GLAD_GL_ARB_separate_shader_objects, is_amd);
|
||||
shader_program_manager = std::make_unique<ShaderProgramManager>(emu_window, false);
|
||||
}
|
||||
#else
|
||||
shader_program_manager = std::make_unique<ShaderProgramManager>(
|
||||
emu_window, GLAD_GL_ARB_separate_shader_objects, is_amd);
|
||||
shader_program_manager = std::make_unique<ShaderProgramManager>(emu_window, !GLES);
|
||||
#endif
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
|
||||
SyncEntireState();
|
||||
// Explicitly call the derived version to avoid warnings about calling virtual
|
||||
// methods in the constructor
|
||||
RasterizerOpenGL::SyncEntireState();
|
||||
}
|
||||
|
||||
RasterizerOpenGL::~RasterizerOpenGL() = default;
|
||||
@ -577,9 +547,10 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
state.Apply();
|
||||
|
||||
if (shadow_rendering) {
|
||||
if (!allow_shadow || color_surface == nullptr) {
|
||||
if (color_surface == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
|
||||
color_surface->width * color_surface->res_scale);
|
||||
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
|
||||
@ -656,6 +627,18 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
}
|
||||
};
|
||||
|
||||
const auto BindCubeFace = [&](GLuint& target, Pica::TexturingRegs::CubeFace face,
|
||||
Pica::Texture::TextureInfo& info) {
|
||||
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
|
||||
Surface surface = res_cache.GetTextureSurface(info);
|
||||
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(target = surface->texture.handle);
|
||||
} else {
|
||||
target = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Sync and bind the texture surfaces
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
|
||||
@ -666,9 +649,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||
switch (texture.config.type.Value()) {
|
||||
case TextureType::Shadow2D: {
|
||||
if (!allow_shadow)
|
||||
continue;
|
||||
|
||||
Surface surface = res_cache.GetTextureSurface(texture);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_px = surface->texture.handle);
|
||||
@ -678,67 +658,15 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
continue;
|
||||
}
|
||||
case TextureType::ShadowCube: {
|
||||
if (!allow_shadow)
|
||||
continue;
|
||||
Pica::Texture::TextureInfo info = Pica::Texture::TextureInfo::FromPicaRegister(
|
||||
texture.config, texture.format);
|
||||
Surface surface;
|
||||
|
||||
using CubeFace = Pica::TexturingRegs::CubeFace;
|
||||
info.physical_address =
|
||||
regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX);
|
||||
surface = res_cache.GetTextureSurface(info);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_px = surface->texture.handle);
|
||||
} else {
|
||||
state.image_shadow_texture_px = 0;
|
||||
}
|
||||
|
||||
info.physical_address =
|
||||
regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX);
|
||||
surface = res_cache.GetTextureSurface(info);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_nx = surface->texture.handle);
|
||||
} else {
|
||||
state.image_shadow_texture_nx = 0;
|
||||
}
|
||||
|
||||
info.physical_address =
|
||||
regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY);
|
||||
surface = res_cache.GetTextureSurface(info);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_py = surface->texture.handle);
|
||||
} else {
|
||||
state.image_shadow_texture_py = 0;
|
||||
}
|
||||
|
||||
info.physical_address =
|
||||
regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY);
|
||||
surface = res_cache.GetTextureSurface(info);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_ny = surface->texture.handle);
|
||||
} else {
|
||||
state.image_shadow_texture_ny = 0;
|
||||
}
|
||||
|
||||
info.physical_address =
|
||||
regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ);
|
||||
surface = res_cache.GetTextureSurface(info);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_pz = surface->texture.handle);
|
||||
} else {
|
||||
state.image_shadow_texture_pz = 0;
|
||||
}
|
||||
|
||||
info.physical_address =
|
||||
regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ);
|
||||
surface = res_cache.GetTextureSurface(info);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_nz = surface->texture.handle);
|
||||
} else {
|
||||
state.image_shadow_texture_nz = 0;
|
||||
}
|
||||
|
||||
auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config,
|
||||
texture.format);
|
||||
BindCubeFace(state.image_shadow_texture_px, CubeFace::PositiveX, info);
|
||||
BindCubeFace(state.image_shadow_texture_nx, CubeFace::NegativeX, info);
|
||||
BindCubeFace(state.image_shadow_texture_py, CubeFace::PositiveY, info);
|
||||
BindCubeFace(state.image_shadow_texture_ny, CubeFace::NegativeY, info);
|
||||
BindCubeFace(state.image_shadow_texture_pz, CubeFace::PositiveZ, info);
|
||||
BindCubeFace(state.image_shadow_texture_nz, CubeFace::NegativeZ, info);
|
||||
continue;
|
||||
}
|
||||
case TextureType::TextureCube:
|
||||
@ -758,7 +686,10 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
texture_cube_sampler.SyncWithConfig(texture.config);
|
||||
state.texture_units[texture_index].texture_2d = 0;
|
||||
continue; // Texture unit 0 setup finished. Continue to next unit
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
state.texture_cube_unit.texture_cube = 0;
|
||||
}
|
||||
|
||||
@ -783,27 +714,19 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
}
|
||||
|
||||
OGLTexture temp_tex;
|
||||
if (need_duplicate_texture && (GLAD_GL_ARB_copy_image || GLES)) {
|
||||
if (need_duplicate_texture) {
|
||||
const auto& tuple = GetFormatTuple(color_surface->pixel_format);
|
||||
const GLsizei levels = color_surface->max_level + 1;
|
||||
|
||||
// The game is trying to use a surface as a texture and framebuffer at the same time
|
||||
// which causes unpredictable behavior on the host.
|
||||
// Making a copy to sample from eliminates this issue and seems to be fairly cheap.
|
||||
temp_tex.Create();
|
||||
glBindTexture(GL_TEXTURE_2D, temp_tex.handle);
|
||||
auto [internal_format, format, type] = GetFormatTuple(color_surface->pixel_format);
|
||||
OGLTexture::Allocate(GL_TEXTURE_2D, color_surface->max_level + 1, internal_format, format,
|
||||
type, color_surface->GetScaledWidth(),
|
||||
color_surface->GetScaledHeight());
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glBindTexture(GL_TEXTURE_2D, state.texture_units[0].texture_2d);
|
||||
temp_tex.Allocate(GL_TEXTURE_2D, levels, tuple.internal_format,
|
||||
color_surface->GetScaledWidth(), color_surface->GetScaledHeight());
|
||||
|
||||
for (u32 level{0}; level <= color_surface->max_level; ++level) {
|
||||
glCopyImageSubData(color_surface->texture.handle, GL_TEXTURE_2D, level, 0, 0, 0,
|
||||
temp_tex.handle, GL_TEXTURE_2D, level, 0, 0, 0,
|
||||
color_surface->GetScaledWidth() >> level,
|
||||
color_surface->GetScaledHeight() >> level, 1);
|
||||
}
|
||||
temp_tex.CopyFrom(color_surface->texture, GL_TEXTURE_2D, levels,
|
||||
color_surface->GetScaledWidth(), color_surface->GetScaledHeight());
|
||||
|
||||
for (auto& unit : state.texture_units) {
|
||||
if (unit.texture_2d == color_surface->texture.handle) {
|
||||
@ -878,15 +801,13 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
state.texture_units[texture_index].texture_2d = 0;
|
||||
}
|
||||
state.texture_cube_unit.texture_cube = 0;
|
||||
if (allow_shadow) {
|
||||
state.image_shadow_texture_px = 0;
|
||||
state.image_shadow_texture_nx = 0;
|
||||
state.image_shadow_texture_py = 0;
|
||||
state.image_shadow_texture_ny = 0;
|
||||
state.image_shadow_texture_pz = 0;
|
||||
state.image_shadow_texture_nz = 0;
|
||||
state.image_shadow_buffer = 0;
|
||||
}
|
||||
state.image_shadow_texture_px = 0;
|
||||
state.image_shadow_texture_nx = 0;
|
||||
state.image_shadow_texture_py = 0;
|
||||
state.image_shadow_texture_ny = 0;
|
||||
state.image_shadow_texture_pz = 0;
|
||||
state.image_shadow_texture_nz = 0;
|
||||
state.image_shadow_buffer = 0;
|
||||
state.Apply();
|
||||
|
||||
if (shadow_rendering) {
|
||||
@ -1445,7 +1366,7 @@ bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransfe
|
||||
src_params.stride = config.input_width;
|
||||
src_params.height = config.output_height;
|
||||
src_params.is_tiled = !config.input_linear;
|
||||
src_params.pixel_format = SurfaceParams::PixelFormatFromGPUPixelFormat(config.input_format);
|
||||
src_params.pixel_format = PixelFormatFromGPUPixelFormat(config.input_format);
|
||||
src_params.UpdateParams();
|
||||
|
||||
SurfaceParams dst_params;
|
||||
@ -1455,7 +1376,7 @@ bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransfe
|
||||
dst_params.height = config.scaling == config.ScaleXY ? config.output_height.Value() / 2
|
||||
: config.output_height.Value();
|
||||
dst_params.is_tiled = config.input_linear != config.dont_swizzle;
|
||||
dst_params.pixel_format = SurfaceParams::PixelFormatFromGPUPixelFormat(config.output_format);
|
||||
dst_params.pixel_format = PixelFormatFromGPUPixelFormat(config.output_format);
|
||||
dst_params.UpdateParams();
|
||||
|
||||
Common::Rectangle<u32> src_rect;
|
||||
@ -1595,7 +1516,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con
|
||||
src_params.height = config.height;
|
||||
src_params.stride = pixel_stride;
|
||||
src_params.is_tiled = false;
|
||||
src_params.pixel_format = SurfaceParams::PixelFormatFromGPUPixelFormat(config.color_format);
|
||||
src_params.pixel_format = PixelFormatFromGPUPixelFormat(config.color_format);
|
||||
src_params.UpdateParams();
|
||||
|
||||
Common::Rectangle<u32> src_rect;
|
||||
@ -1674,7 +1595,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
|
||||
if (border_color != config.border_color.raw) {
|
||||
border_color = config.border_color.raw;
|
||||
auto gl_color = PicaToGL::ColorRGBA8(border_color);
|
||||
glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.data());
|
||||
glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.AsArray());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1704,8 +1625,8 @@ void RasterizerOpenGL::SyncClipEnabled() {
|
||||
|
||||
void RasterizerOpenGL::SyncClipCoef() {
|
||||
const auto raw_clip_coef = Pica::g_state.regs.rasterizer.GetClipCoef();
|
||||
const GLvec4 new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(),
|
||||
raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()};
|
||||
const Common::Vec4f new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(),
|
||||
raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()};
|
||||
if (new_clip_coef != uniform_block_data.data.clip_coef) {
|
||||
uniform_block_data.data.clip_coef = new_clip_coef;
|
||||
uniform_block_data.dirty = true;
|
||||
@ -1975,10 +1896,10 @@ void RasterizerOpenGL::SyncLightAmbient(int light_index) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLightPosition(int light_index) {
|
||||
GLvec3 position = {
|
||||
Pica::float16::FromRaw(Pica::g_state.regs.lighting.light[light_index].x).ToFloat32(),
|
||||
Pica::float16::FromRaw(Pica::g_state.regs.lighting.light[light_index].y).ToFloat32(),
|
||||
Pica::float16::FromRaw(Pica::g_state.regs.lighting.light[light_index].z).ToFloat32()};
|
||||
const auto& light = Pica::g_state.regs.lighting.light[light_index];
|
||||
const Common::Vec3f position = {Pica::float16::FromRaw(light.x).ToFloat32(),
|
||||
Pica::float16::FromRaw(light.y).ToFloat32(),
|
||||
Pica::float16::FromRaw(light.z).ToFloat32()};
|
||||
|
||||
if (position != uniform_block_data.data.light_src[light_index].position) {
|
||||
uniform_block_data.data.light_src[light_index].position = position;
|
||||
@ -1988,8 +1909,7 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) {
|
||||
|
||||
void RasterizerOpenGL::SyncLightSpotDirection(int light_index) {
|
||||
const auto& light = Pica::g_state.regs.lighting.light[light_index];
|
||||
GLvec3 spot_direction = {light.spot_x / 2047.0f, light.spot_y / 2047.0f,
|
||||
light.spot_z / 2047.0f};
|
||||
const auto spot_direction = Common::Vec3u{light.spot_x, light.spot_y, light.spot_z} / 2047.0f;
|
||||
|
||||
if (spot_direction != uniform_block_data.data.light_src[light_index].spot_direction) {
|
||||
uniform_block_data.data.light_src[light_index].spot_direction = spot_direction;
|
||||
@ -1998,9 +1918,8 @@ void RasterizerOpenGL::SyncLightSpotDirection(int light_index) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) {
|
||||
GLfloat dist_atten_bias =
|
||||
Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias)
|
||||
.ToFloat32();
|
||||
const auto& light = Pica::g_state.regs.lighting.light[light_index];
|
||||
float dist_atten_bias = Pica::float20::FromRaw(light.dist_atten_bias).ToFloat32();
|
||||
|
||||
if (dist_atten_bias != uniform_block_data.data.light_src[light_index].dist_atten_bias) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_bias = dist_atten_bias;
|
||||
@ -2009,9 +1928,8 @@ void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncLightDistanceAttenuationScale(int light_index) {
|
||||
GLfloat dist_atten_scale =
|
||||
Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_scale)
|
||||
.ToFloat32();
|
||||
const auto& light = Pica::g_state.regs.lighting.light[light_index];
|
||||
float dist_atten_scale = Pica::float20::FromRaw(light.dist_atten_scale).ToFloat32();
|
||||
|
||||
if (dist_atten_scale != uniform_block_data.data.light_src[light_index].dist_atten_scale) {
|
||||
uniform_block_data.data.light_src[light_index].dist_atten_scale = dist_atten_scale;
|
||||
@ -2042,7 +1960,8 @@ void RasterizerOpenGL::SyncShadowTextureBias() {
|
||||
|
||||
void RasterizerOpenGL::SyncAndUploadLUTsLF() {
|
||||
constexpr std::size_t max_size =
|
||||
sizeof(GLvec2) * 256 * Pica::LightingRegs::NumLightingSampler + sizeof(GLvec2) * 128; // fog
|
||||
sizeof(Common::Vec2f) * 256 * Pica::LightingRegs::NumLightingSampler +
|
||||
sizeof(Common::Vec2f) * 128; // fog
|
||||
|
||||
if (!uniform_block_data.lighting_lut_dirty_any && !uniform_block_data.fog_lut_dirty) {
|
||||
return;
|
||||
@ -2053,27 +1972,27 @@ void RasterizerOpenGL::SyncAndUploadLUTsLF() {
|
||||
bool invalidate;
|
||||
std::size_t bytes_used = 0;
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, texture_lf_buffer.GetHandle());
|
||||
std::tie(buffer, offset, invalidate) = texture_lf_buffer.Map(max_size, sizeof(GLvec4));
|
||||
std::tie(buffer, offset, invalidate) = texture_lf_buffer.Map(max_size, sizeof(Common::Vec4f));
|
||||
|
||||
// Sync the lighting luts
|
||||
if (uniform_block_data.lighting_lut_dirty_any || invalidate) {
|
||||
for (unsigned index = 0; index < uniform_block_data.lighting_lut_dirty.size(); index++) {
|
||||
if (uniform_block_data.lighting_lut_dirty[index] || invalidate) {
|
||||
std::array<GLvec2, 256> new_data;
|
||||
std::array<Common::Vec2f, 256> new_data;
|
||||
const auto& source_lut = Pica::g_state.lighting.luts[index];
|
||||
std::transform(source_lut.begin(), source_lut.end(), new_data.begin(),
|
||||
[](const auto& entry) {
|
||||
return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
|
||||
return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()};
|
||||
});
|
||||
|
||||
if (new_data != lighting_lut_data[index] || invalidate) {
|
||||
lighting_lut_data[index] = new_data;
|
||||
std::memcpy(buffer + bytes_used, new_data.data(),
|
||||
new_data.size() * sizeof(GLvec2));
|
||||
new_data.size() * sizeof(Common::Vec2f));
|
||||
uniform_block_data.data.lighting_lut_offset[index / 4][index % 4] =
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(GLvec2));
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(Common::Vec2f));
|
||||
uniform_block_data.dirty = true;
|
||||
bytes_used += new_data.size() * sizeof(GLvec2);
|
||||
bytes_used += new_data.size() * sizeof(Common::Vec2f);
|
||||
}
|
||||
uniform_block_data.lighting_lut_dirty[index] = false;
|
||||
}
|
||||
@ -2083,20 +2002,21 @@ void RasterizerOpenGL::SyncAndUploadLUTsLF() {
|
||||
|
||||
// Sync the fog lut
|
||||
if (uniform_block_data.fog_lut_dirty || invalidate) {
|
||||
std::array<GLvec2, 128> new_data;
|
||||
std::array<Common::Vec2f, 128> new_data;
|
||||
|
||||
std::transform(Pica::g_state.fog.lut.begin(), Pica::g_state.fog.lut.end(), new_data.begin(),
|
||||
[](const auto& entry) {
|
||||
return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
|
||||
return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()};
|
||||
});
|
||||
|
||||
if (new_data != fog_lut_data || invalidate) {
|
||||
fog_lut_data = new_data;
|
||||
std::memcpy(buffer + bytes_used, new_data.data(), new_data.size() * sizeof(GLvec2));
|
||||
std::memcpy(buffer + bytes_used, new_data.data(),
|
||||
new_data.size() * sizeof(Common::Vec2f));
|
||||
uniform_block_data.data.fog_lut_offset =
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(GLvec2));
|
||||
static_cast<int>((offset + bytes_used) / sizeof(Common::Vec2f));
|
||||
uniform_block_data.dirty = true;
|
||||
bytes_used += new_data.size() * sizeof(GLvec2);
|
||||
bytes_used += new_data.size() * sizeof(Common::Vec2f);
|
||||
}
|
||||
uniform_block_data.fog_lut_dirty = false;
|
||||
}
|
||||
@ -2105,9 +2025,10 @@ void RasterizerOpenGL::SyncAndUploadLUTsLF() {
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::SyncAndUploadLUTs() {
|
||||
constexpr std::size_t max_size = sizeof(GLvec2) * 128 * 3 + // proctex: noise + color + alpha
|
||||
sizeof(GLvec4) * 256 + // proctex
|
||||
sizeof(GLvec4) * 256; // proctex diff
|
||||
constexpr std::size_t max_size =
|
||||
sizeof(Common::Vec2f) * 128 * 3 + // proctex: noise + color + alpha
|
||||
sizeof(Common::Vec4f) * 256 + // proctex
|
||||
sizeof(Common::Vec4f) * 256; // proctex diff
|
||||
|
||||
if (!uniform_block_data.proctex_noise_lut_dirty &&
|
||||
!uniform_block_data.proctex_color_map_dirty &&
|
||||
@ -2121,23 +2042,24 @@ void RasterizerOpenGL::SyncAndUploadLUTs() {
|
||||
bool invalidate;
|
||||
std::size_t bytes_used = 0;
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, texture_buffer.GetHandle());
|
||||
std::tie(buffer, offset, invalidate) = texture_buffer.Map(max_size, sizeof(GLvec4));
|
||||
std::tie(buffer, offset, invalidate) = texture_buffer.Map(max_size, sizeof(Common::Vec4f));
|
||||
|
||||
// helper function for SyncProcTexNoiseLUT/ColorMap/AlphaMap
|
||||
auto SyncProcTexValueLUT = [this, buffer, offset, invalidate, &bytes_used](
|
||||
const std::array<Pica::State::ProcTex::ValueEntry, 128>& lut,
|
||||
std::array<GLvec2, 128>& lut_data, GLint& lut_offset) {
|
||||
std::array<GLvec2, 128> new_data;
|
||||
std::array<Common::Vec2f, 128>& lut_data, GLint& lut_offset) {
|
||||
std::array<Common::Vec2f, 128> new_data;
|
||||
std::transform(lut.begin(), lut.end(), new_data.begin(), [](const auto& entry) {
|
||||
return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
|
||||
return Common::Vec2f{entry.ToFloat(), entry.DiffToFloat()};
|
||||
});
|
||||
|
||||
if (new_data != lut_data || invalidate) {
|
||||
lut_data = new_data;
|
||||
std::memcpy(buffer + bytes_used, new_data.data(), new_data.size() * sizeof(GLvec2));
|
||||
lut_offset = static_cast<GLint>((offset + bytes_used) / sizeof(GLvec2));
|
||||
std::memcpy(buffer + bytes_used, new_data.data(),
|
||||
new_data.size() * sizeof(Common::Vec2f));
|
||||
lut_offset = static_cast<GLint>((offset + bytes_used) / sizeof(Common::Vec2f));
|
||||
uniform_block_data.dirty = true;
|
||||
bytes_used += new_data.size() * sizeof(GLvec2);
|
||||
bytes_used += new_data.size() * sizeof(Common::Vec2f);
|
||||
}
|
||||
};
|
||||
|
||||
@ -2164,44 +2086,46 @@ void RasterizerOpenGL::SyncAndUploadLUTs() {
|
||||
|
||||
// Sync the proctex lut
|
||||
if (uniform_block_data.proctex_lut_dirty || invalidate) {
|
||||
std::array<GLvec4, 256> new_data;
|
||||
std::array<Common::Vec4f, 256> new_data;
|
||||
|
||||
std::transform(Pica::g_state.proctex.color_table.begin(),
|
||||
Pica::g_state.proctex.color_table.end(), new_data.begin(),
|
||||
[](const auto& entry) {
|
||||
auto rgba = entry.ToVector() / 255.0f;
|
||||
return GLvec4{rgba.r(), rgba.g(), rgba.b(), rgba.a()};
|
||||
return Common::Vec4f{rgba.r(), rgba.g(), rgba.b(), rgba.a()};
|
||||
});
|
||||
|
||||
if (new_data != proctex_lut_data || invalidate) {
|
||||
proctex_lut_data = new_data;
|
||||
std::memcpy(buffer + bytes_used, new_data.data(), new_data.size() * sizeof(GLvec4));
|
||||
std::memcpy(buffer + bytes_used, new_data.data(),
|
||||
new_data.size() * sizeof(Common::Vec4f));
|
||||
uniform_block_data.data.proctex_lut_offset =
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(GLvec4));
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(Common::Vec4f));
|
||||
uniform_block_data.dirty = true;
|
||||
bytes_used += new_data.size() * sizeof(GLvec4);
|
||||
bytes_used += new_data.size() * sizeof(Common::Vec4f);
|
||||
}
|
||||
uniform_block_data.proctex_lut_dirty = false;
|
||||
}
|
||||
|
||||
// Sync the proctex difference lut
|
||||
if (uniform_block_data.proctex_diff_lut_dirty || invalidate) {
|
||||
std::array<GLvec4, 256> new_data;
|
||||
std::array<Common::Vec4f, 256> new_data;
|
||||
|
||||
std::transform(Pica::g_state.proctex.color_diff_table.begin(),
|
||||
Pica::g_state.proctex.color_diff_table.end(), new_data.begin(),
|
||||
[](const auto& entry) {
|
||||
auto rgba = entry.ToVector() / 255.0f;
|
||||
return GLvec4{rgba.r(), rgba.g(), rgba.b(), rgba.a()};
|
||||
return Common::Vec4f{rgba.r(), rgba.g(), rgba.b(), rgba.a()};
|
||||
});
|
||||
|
||||
if (new_data != proctex_diff_lut_data || invalidate) {
|
||||
proctex_diff_lut_data = new_data;
|
||||
std::memcpy(buffer + bytes_used, new_data.data(), new_data.size() * sizeof(GLvec4));
|
||||
std::memcpy(buffer + bytes_used, new_data.data(),
|
||||
new_data.size() * sizeof(Common::Vec4f));
|
||||
uniform_block_data.data.proctex_diff_lut_offset =
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(GLvec4));
|
||||
static_cast<GLint>((offset + bytes_used) / sizeof(Common::Vec4f));
|
||||
uniform_block_data.dirty = true;
|
||||
bytes_used += new_data.size() * sizeof(GLvec4);
|
||||
bytes_used += new_data.size() * sizeof(Common::Vec4f);
|
||||
}
|
||||
uniform_block_data.proctex_diff_lut_dirty = false;
|
||||
}
|
||||
|
@ -1,32 +1,18 @@
|
||||
// Copyright 2015 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 <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/pica_types.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_rasterizer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_stream_buffer.h"
|
||||
#include "video_core/renderer_opengl/pica_to_gl.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
||||
namespace Frontend {
|
||||
@ -118,20 +104,18 @@ private:
|
||||
view[2] = v.view.z.ToFloat32();
|
||||
|
||||
if (flip_quaternion) {
|
||||
for (float& x : normquat) {
|
||||
x = -x;
|
||||
}
|
||||
normquat = -normquat;
|
||||
}
|
||||
}
|
||||
|
||||
GLvec4 position;
|
||||
GLvec4 color;
|
||||
GLvec2 tex_coord0;
|
||||
GLvec2 tex_coord1;
|
||||
GLvec2 tex_coord2;
|
||||
GLfloat tex_coord0_w;
|
||||
GLvec4 normquat;
|
||||
GLvec3 view;
|
||||
Common::Vec4f position;
|
||||
Common::Vec4f color;
|
||||
Common::Vec2f tex_coord0;
|
||||
Common::Vec2f tex_coord1;
|
||||
Common::Vec2f tex_coord2;
|
||||
float tex_coord0_w;
|
||||
Common::Vec4f normquat;
|
||||
Common::Vec3f view;
|
||||
};
|
||||
|
||||
/// Syncs the clip enabled status to match the PICA register
|
||||
@ -263,8 +247,6 @@ private:
|
||||
/// Setup geometry shader for AccelerateDrawBatch
|
||||
bool SetupGeometryShader();
|
||||
|
||||
bool is_amd;
|
||||
|
||||
OpenGLState state;
|
||||
GLuint default_texture;
|
||||
|
||||
@ -316,15 +298,14 @@ private:
|
||||
OGLTexture texture_buffer_lut_rg;
|
||||
OGLTexture texture_buffer_lut_rgba;
|
||||
|
||||
std::array<std::array<GLvec2, 256>, Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
|
||||
std::array<GLvec2, 128> fog_lut_data{};
|
||||
std::array<GLvec2, 128> proctex_noise_lut_data{};
|
||||
std::array<GLvec2, 128> proctex_color_map_data{};
|
||||
std::array<GLvec2, 128> proctex_alpha_map_data{};
|
||||
std::array<GLvec4, 256> proctex_lut_data{};
|
||||
std::array<GLvec4, 256> proctex_diff_lut_data{};
|
||||
|
||||
bool allow_shadow;
|
||||
std::array<std::array<Common::Vec2f, 256>, Pica::LightingRegs::NumLightingSampler>
|
||||
lighting_lut_data{};
|
||||
std::array<Common::Vec2f, 128> fog_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_noise_lut_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_color_map_data{};
|
||||
std::array<Common::Vec2f, 128> proctex_alpha_map_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_lut_data{};
|
||||
std::array<Common::Vec4f, 256> proctex_diff_lut_data{};
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,390 +0,0 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
|
||||
#endif
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#ifdef __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
#include <unordered_map>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_surface_params.h"
|
||||
#include "video_core/texture/texture_decode.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class RasterizerCacheOpenGL;
|
||||
class TextureFilterer;
|
||||
class FormatReinterpreterOpenGL;
|
||||
|
||||
struct FormatTuple {
|
||||
GLint internal_format;
|
||||
GLenum format;
|
||||
GLenum type;
|
||||
};
|
||||
|
||||
constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
|
||||
const FormatTuple& GetFormatTuple(SurfaceParams::PixelFormat pixel_format);
|
||||
|
||||
struct HostTextureTag {
|
||||
FormatTuple format_tuple;
|
||||
u32 width;
|
||||
u32 height;
|
||||
bool operator==(const HostTextureTag& rhs) const noexcept {
|
||||
return std::tie(format_tuple.format, format_tuple.internal_format, width, height) ==
|
||||
std::tie(rhs.format_tuple.format, rhs.format_tuple.internal_format, rhs.width,
|
||||
rhs.height);
|
||||
};
|
||||
};
|
||||
|
||||
struct TextureCubeConfig {
|
||||
PAddr px;
|
||||
PAddr nx;
|
||||
PAddr py;
|
||||
PAddr ny;
|
||||
PAddr pz;
|
||||
PAddr nz;
|
||||
u32 width;
|
||||
Pica::TexturingRegs::TextureFormat format;
|
||||
|
||||
bool operator==(const TextureCubeConfig& rhs) const {
|
||||
return std::tie(px, nx, py, ny, pz, nz, width, format) ==
|
||||
std::tie(rhs.px, rhs.nx, rhs.py, rhs.ny, rhs.pz, rhs.nz, rhs.width, rhs.format);
|
||||
}
|
||||
|
||||
bool operator!=(const TextureCubeConfig& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<OpenGL::HostTextureTag> {
|
||||
std::size_t operator()(const OpenGL::HostTextureTag& tag) const noexcept {
|
||||
std::size_t hash = 0;
|
||||
boost::hash_combine(hash, tag.format_tuple.format);
|
||||
boost::hash_combine(hash, tag.format_tuple.internal_format);
|
||||
boost::hash_combine(hash, tag.width);
|
||||
boost::hash_combine(hash, tag.height);
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::TextureCubeConfig> {
|
||||
std::size_t operator()(const OpenGL::TextureCubeConfig& config) const noexcept {
|
||||
std::size_t hash = 0;
|
||||
boost::hash_combine(hash, config.px);
|
||||
boost::hash_combine(hash, config.nx);
|
||||
boost::hash_combine(hash, config.py);
|
||||
boost::hash_combine(hash, config.ny);
|
||||
boost::hash_combine(hash, config.pz);
|
||||
boost::hash_combine(hash, config.nz);
|
||||
boost::hash_combine(hash, config.width);
|
||||
boost::hash_combine(hash, static_cast<u32>(config.format));
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
} // namespace std
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using SurfaceSet = std::set<Surface>;
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
using SurfaceMap =
|
||||
boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
using SurfaceCache =
|
||||
boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
|
||||
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
|
||||
|
||||
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
|
||||
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
|
||||
"incorrect interval types");
|
||||
|
||||
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
|
||||
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
|
||||
|
||||
using PageMap = boost::icl::interval_map<u32, int>;
|
||||
|
||||
enum class ScaleMatch {
|
||||
Exact, // only accept same res scale
|
||||
Upscale, // only allow higher scale than params
|
||||
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;
|
||||
|
||||
static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) {
|
||||
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
|
||||
return format == PixelFormat::Invalid
|
||||
? 0
|
||||
: (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture)
|
||||
? 4
|
||||
: SurfaceParams::GetFormatBpp(format) / 8;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
|
||||
{},
|
||||
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
|
||||
}};
|
||||
|
||||
class TextureDownloaderES;
|
||||
|
||||
class RasterizerCacheOpenGL : NonCopyable {
|
||||
public:
|
||||
RasterizerCacheOpenGL();
|
||||
~RasterizerCacheOpenGL();
|
||||
|
||||
/// Blit one surface's texture to another
|
||||
bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect,
|
||||
const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect);
|
||||
|
||||
/// Copy one surface's region to another
|
||||
void CopySurface(const Surface& src_surface, const Surface& dst_surface,
|
||||
SurfaceInterval copy_interval);
|
||||
|
||||
/// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
|
||||
Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
/// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
|
||||
/// 3DS memory to OpenGL and caches it (if not already cached)
|
||||
SurfaceRect_Tuple GetSurfaceSubRect(const SurfaceParams& params, ScaleMatch match_res_scale,
|
||||
bool load_if_create);
|
||||
|
||||
/// Get a surface based on the texture configuration
|
||||
Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
|
||||
Surface GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
|
||||
|
||||
/// Get a texture cube based on the texture configuration
|
||||
const CachedTextureCube& GetTextureCube(const TextureCubeConfig& config);
|
||||
|
||||
/// Get the color and depth surfaces based on the framebuffer configuration
|
||||
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
|
||||
const Common::Rectangle<s32>& viewport_rect);
|
||||
|
||||
/// Get a surface that matches the fill config
|
||||
Surface GetFillSurface(const GPU::Regs::MemoryFillConfig& config);
|
||||
|
||||
/// Get a surface that matches a "texture copy" display transfer config
|
||||
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
|
||||
|
||||
/// Write any cached resources overlapping the region back to memory (if dirty)
|
||||
void FlushRegion(PAddr addr, u32 size, Surface flush_surface = nullptr);
|
||||
|
||||
/// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
|
||||
void InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner);
|
||||
|
||||
/// Flush all cached resources tracked by this cache manager
|
||||
void FlushAll();
|
||||
|
||||
/// Clear all cached resources tracked by this cache manager
|
||||
void ClearAll(bool flush);
|
||||
|
||||
// Textures from destroyed surfaces are stored here to be recyled to reduce allocation overhead
|
||||
// in the driver
|
||||
// this must be placed above the surface_cache to ensure all cached surfaces are destroyed
|
||||
// before destroying the recycler
|
||||
std::unordered_multimap<HostTextureTag, OGLTexture> host_texture_recycler;
|
||||
|
||||
private:
|
||||
void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
|
||||
|
||||
/// Update surface's texture for given region when necessary
|
||||
void ValidateSurface(const Surface& surface, PAddr addr, u32 size);
|
||||
|
||||
// Returns false if there is a surface in the cache at the interval with the same bit-width,
|
||||
bool NoUnimplementedReinterpretations(const OpenGL::Surface& surface,
|
||||
OpenGL::SurfaceParams& params,
|
||||
const OpenGL::SurfaceInterval& interval);
|
||||
|
||||
// Return true if a surface with an invalid pixel format exists at the interval
|
||||
bool IntervalHasInvalidPixelFormat(SurfaceParams& params, const SurfaceInterval& interval);
|
||||
|
||||
// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
|
||||
bool ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
|
||||
const SurfaceInterval& interval);
|
||||
|
||||
/// Create a new surface
|
||||
Surface CreateSurface(const SurfaceParams& params);
|
||||
|
||||
/// Register surface into the cache
|
||||
void RegisterSurface(const Surface& surface);
|
||||
|
||||
/// Remove surface from the cache
|
||||
void UnregisterSurface(const Surface& surface);
|
||||
|
||||
/// Increase/decrease the number of surface in pages touching the specified region
|
||||
void UpdatePagesCachedCount(PAddr addr, u32 size, int delta);
|
||||
|
||||
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;
|
||||
|
||||
std::recursive_mutex mutex;
|
||||
|
||||
public:
|
||||
OGLTexture AllocateSurfaceTexture(const FormatTuple& format_tuple, u32 width, u32 height);
|
||||
|
||||
std::unique_ptr<TextureFilterer> texture_filterer;
|
||||
std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter;
|
||||
std::unique_ptr<TextureDownloaderES> texture_downloader_es;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
@ -1,15 +1,11 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <utility>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_ResourceCreation, "OpenGL", "Resource Creation", MP_RGB(128, 128, 192));
|
||||
MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_RGB(128, 128, 192));
|
||||
@ -17,16 +13,18 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R
|
||||
namespace OpenGL {
|
||||
|
||||
void OGLRenderbuffer::Create() {
|
||||
if (handle != 0)
|
||||
if (handle != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenRenderbuffers(1, &handle);
|
||||
}
|
||||
|
||||
void OGLRenderbuffer::Release() {
|
||||
if (handle == 0)
|
||||
if (handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteRenderbuffers(1, &handle);
|
||||
@ -35,16 +33,18 @@ void OGLRenderbuffer::Release() {
|
||||
}
|
||||
|
||||
void OGLTexture::Create() {
|
||||
if (handle != 0)
|
||||
if (handle != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenTextures(1, &handle);
|
||||
}
|
||||
|
||||
void OGLTexture::Release() {
|
||||
if (handle == 0)
|
||||
if (handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteTextures(1, &handle);
|
||||
@ -52,72 +52,64 @@ void OGLTexture::Release() {
|
||||
handle = 0;
|
||||
}
|
||||
|
||||
void OGLTexture::Allocate(GLenum target, GLsizei levels, GLenum internalformat, GLenum format,
|
||||
GLenum type, GLsizei width, GLsizei height, GLsizei depth) {
|
||||
const bool tex_storage = GLAD_GL_ARB_texture_storage || GLES;
|
||||
void OGLTexture::Allocate(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width,
|
||||
GLsizei height, GLsizei depth) {
|
||||
GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, handle);
|
||||
|
||||
switch (target) {
|
||||
case GL_TEXTURE_1D:
|
||||
case GL_TEXTURE:
|
||||
if (tex_storage) {
|
||||
glTexStorage1D(target, levels, internalformat, width);
|
||||
} else {
|
||||
for (GLsizei level{0}; level < levels; ++level) {
|
||||
glTexImage1D(target, level, internalformat, width, 0, format, type, nullptr);
|
||||
width >>= 1;
|
||||
}
|
||||
}
|
||||
glTexStorage1D(target, levels, internalformat, width);
|
||||
break;
|
||||
case GL_TEXTURE_2D:
|
||||
case GL_TEXTURE_1D_ARRAY:
|
||||
case GL_TEXTURE_RECTANGLE:
|
||||
case GL_TEXTURE_CUBE_MAP:
|
||||
if (tex_storage) {
|
||||
glTexStorage2D(target, levels, internalformat, width, height);
|
||||
} else {
|
||||
for (GLsizei level{0}; level < levels; ++level) {
|
||||
glTexImage2D(target, level, internalformat, width, height, 0, format, type,
|
||||
nullptr);
|
||||
width >>= 1;
|
||||
if (target != GL_TEXTURE_1D_ARRAY)
|
||||
height >>= 1;
|
||||
}
|
||||
}
|
||||
glTexStorage2D(target, levels, internalformat, width, height);
|
||||
break;
|
||||
case GL_TEXTURE_3D:
|
||||
case GL_TEXTURE_2D_ARRAY:
|
||||
case GL_TEXTURE_CUBE_MAP_ARRAY:
|
||||
if (tex_storage) {
|
||||
glTexStorage3D(target, levels, internalformat, width, height, depth);
|
||||
} else {
|
||||
for (GLsizei level{0}; level < levels; ++level) {
|
||||
glTexImage3D(target, level, internalformat, width, height, depth, 0, format, type,
|
||||
nullptr);
|
||||
}
|
||||
width >>= 1;
|
||||
height >>= 1;
|
||||
if (target == GL_TEXTURE_3D)
|
||||
depth >>= 1;
|
||||
}
|
||||
glTexStorage3D(target, levels, internalformat, width, height, depth);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!tex_storage) {
|
||||
glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, levels - 1);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
||||
}
|
||||
|
||||
void OGLTexture::CopyFrom(const OGLTexture& other, GLenum target, GLsizei levels, GLsizei width,
|
||||
GLsizei height) {
|
||||
GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, handle);
|
||||
|
||||
for (u32 level = 0; level < levels; level++) {
|
||||
glCopyImageSubData(other.handle, target, level, 0, 0, 0, handle, target, level, 0, 0, 0,
|
||||
width >> level, height >> level, 1);
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, old_tex);
|
||||
}
|
||||
|
||||
void OGLSampler::Create() {
|
||||
if (handle != 0)
|
||||
if (handle != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||
glGenSamplers(1, &handle);
|
||||
}
|
||||
|
||||
void OGLSampler::Release() {
|
||||
if (handle == 0)
|
||||
if (handle == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||
glDeleteSamplers(1, &handle);
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
@ -59,8 +58,11 @@ public:
|
||||
/// Deletes the internal OpenGL resource
|
||||
void Release();
|
||||
|
||||
static void Allocate(GLenum target, GLsizei levels, GLenum internalformat, GLenum format,
|
||||
GLenum type, GLsizei width, GLsizei height = 1, GLsizei depth = 1);
|
||||
void Allocate(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width,
|
||||
GLsizei height = 1, GLsizei depth = 1);
|
||||
|
||||
void CopyFrom(const OGLTexture& other, GLenum target, GLsizei levels, GLsizei width,
|
||||
GLsizei height);
|
||||
|
||||
GLuint handle = 0;
|
||||
};
|
||||
|
@ -1,21 +1,13 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <fmt/format.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/bit_set.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_rasterizer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer.h"
|
||||
#include "video_core/pica_state.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
@ -1233,18 +1225,6 @@ ShaderDecompiler::ProgramResult GenerateFragmentShader(const PicaFSConfig& confi
|
||||
const auto& state = config.state;
|
||||
std::string out;
|
||||
|
||||
if (GLES) {
|
||||
out += R"(
|
||||
#define ALLOW_SHADOW (defined(CITRA_GLES))
|
||||
)";
|
||||
} else {
|
||||
out += R"(
|
||||
#extension GL_ARB_shader_image_load_store : enable
|
||||
#extension GL_ARB_shader_image_size : enable
|
||||
#define ALLOW_SHADOW (defined(GL_ARB_shader_image_load_store) && defined(GL_ARB_shader_image_size))
|
||||
)";
|
||||
}
|
||||
|
||||
if (separable_shader && !GLES) {
|
||||
out += "#extension GL_ARB_separate_shader_objects : enable\n";
|
||||
}
|
||||
@ -1270,7 +1250,6 @@ uniform samplerBuffer texture_buffer_lut_lf;
|
||||
uniform samplerBuffer texture_buffer_lut_rg;
|
||||
uniform samplerBuffer texture_buffer_lut_rgba;
|
||||
|
||||
#if ALLOW_SHADOW
|
||||
layout(r32ui) uniform readonly uimage2D shadow_texture_px;
|
||||
layout(r32ui) uniform readonly uimage2D shadow_texture_nx;
|
||||
layout(r32ui) uniform readonly uimage2D shadow_texture_py;
|
||||
@ -1278,7 +1257,6 @@ layout(r32ui) uniform readonly uimage2D shadow_texture_ny;
|
||||
layout(r32ui) uniform readonly uimage2D shadow_texture_pz;
|
||||
layout(r32ui) uniform readonly uimage2D shadow_texture_nz;
|
||||
layout(r32ui) uniform uimage2D shadow_buffer;
|
||||
#endif
|
||||
)";
|
||||
|
||||
out += UniformBlockDef;
|
||||
@ -1332,8 +1310,6 @@ float getLod(vec2 coord) {
|
||||
return log2(max(d.x, d.y));
|
||||
}
|
||||
|
||||
#if ALLOW_SHADOW
|
||||
|
||||
uvec2 DecodeShadow(uint pixel) {
|
||||
return uvec2(pixel >> 8, pixel & 0xFFu);
|
||||
}
|
||||
@ -1459,18 +1435,6 @@ vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
CompareShadow(pixels.w, z));
|
||||
return vec4(mix2(s, f));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
vec4 shadowTexture(vec2 uv, float w) {
|
||||
return vec4(1.0);
|
||||
}
|
||||
|
||||
vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
return vec4(1.0);
|
||||
}
|
||||
|
||||
#endif
|
||||
)";
|
||||
|
||||
if (config.state.proctex.enable)
|
||||
@ -1559,7 +1523,6 @@ vec4 secondary_fragment_color = vec4(0.0);
|
||||
|
||||
if (state.shadow_rendering) {
|
||||
out += R"(
|
||||
#if ALLOW_SHADOW
|
||||
uint d = uint(clamp(depth, 0.0, 1.0) * float(0xFFFFFF));
|
||||
uint s = uint(last_tex_env_out.g * float(0xFF));
|
||||
ivec2 image_coord = ivec2(gl_FragCoord.xy);
|
||||
@ -1582,7 +1545,6 @@ do {
|
||||
new = EncodeShadow(ref);
|
||||
|
||||
} while ((old = imageAtomicCompSwap(shadow_buffer, image_coord, old, new)) != old2);
|
||||
#endif // ALLOW_SHADOW
|
||||
)";
|
||||
} else {
|
||||
out += "gl_FragDepth = depth;\n";
|
||||
|
@ -3,13 +3,8 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include "common/hash.h"
|
||||
#include "video_core/regs.h"
|
||||
#include "video_core/shader/shader.h"
|
||||
|
@ -1,16 +1,18 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <boost/functional/hash.hpp>
|
||||
#include <boost/variant.hpp>
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/scope_acquire_context.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
@ -149,11 +151,11 @@ void PicaUniformsData::SetFromRegs(const Pica::ShaderRegs& regs,
|
||||
std::transform(std::begin(setup.uniforms.b), std::end(setup.uniforms.b), std::begin(bools),
|
||||
[](bool value) -> BoolAligned { return {value ? GL_TRUE : GL_FALSE}; });
|
||||
std::transform(std::begin(regs.int_uniforms), std::end(regs.int_uniforms), std::begin(i),
|
||||
[](const auto& value) -> GLuvec4 {
|
||||
[](const auto& value) -> Common::Vec4u {
|
||||
return {value.x.Value(), value.y.Value(), value.z.Value(), value.w.Value()};
|
||||
});
|
||||
std::transform(std::begin(setup.uniforms.f), std::end(setup.uniforms.f), std::begin(f),
|
||||
[](const auto& value) -> GLvec4 {
|
||||
[](const auto& value) -> Common::Vec4f {
|
||||
return {value.x.ToFloat32(), value.y.ToFloat32(), value.z.ToFloat32(),
|
||||
value.w.ToFloat32()};
|
||||
});
|
||||
@ -325,8 +327,8 @@ using FragmentShaders = ShaderCache<PicaFSConfig, &GenerateFragmentShader, GL_FR
|
||||
|
||||
class ShaderProgramManager::Impl {
|
||||
public:
|
||||
explicit Impl(bool separable, bool is_amd)
|
||||
: is_amd(is_amd), separable(separable), programmable_vertex_shaders(separable),
|
||||
explicit Impl(bool separable)
|
||||
: separable(separable), programmable_vertex_shaders(separable),
|
||||
trivial_vertex_shader(separable), fixed_geometry_shaders(separable),
|
||||
fragment_shaders(separable), disk_cache(separable) {
|
||||
if (separable)
|
||||
@ -359,7 +361,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
bool is_amd;
|
||||
bool separable;
|
||||
|
||||
ShaderTuple current;
|
||||
@ -375,9 +376,8 @@ public:
|
||||
ShaderDiskCache disk_cache;
|
||||
};
|
||||
|
||||
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable,
|
||||
bool is_amd)
|
||||
: impl(std::make_unique<Impl>(separable, is_amd)), emu_window{emu_window_} {}
|
||||
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable)
|
||||
: impl(std::make_unique<Impl>(separable)), emu_window{emu_window_} {}
|
||||
|
||||
ShaderProgramManager::~ShaderProgramManager() = default;
|
||||
|
||||
@ -439,15 +439,6 @@ void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) {
|
||||
|
||||
void ShaderProgramManager::ApplyTo(OpenGLState& state) {
|
||||
if (impl->separable) {
|
||||
if (impl->is_amd) {
|
||||
// Without this reseting, AMD sometimes freezes when one stage is changed but not
|
||||
// for the others. On the other hand, including this reset seems to introduce memory
|
||||
// leak in Intel Graphics.
|
||||
glUseProgramStages(
|
||||
impl->pipeline.handle,
|
||||
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0);
|
||||
}
|
||||
|
||||
glUseProgramStages(impl->pipeline.handle, GL_VERTEX_SHADER_BIT, impl->current.vs);
|
||||
glUseProgramStages(impl->pipeline.handle, GL_GEOMETRY_SHADER_BIT, impl->current.gs);
|
||||
glUseProgramStages(impl->pipeline.handle, GL_FRAGMENT_SHADER_BIT, impl->current.fs);
|
||||
@ -471,12 +462,6 @@ void ShaderProgramManager::ApplyTo(OpenGLState& state) {
|
||||
|
||||
void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
const VideoCore::DiskResourceLoadCallback& callback) {
|
||||
if (!GLAD_GL_ARB_get_program_binary && !GLES) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
"Cannot load disk cache as ARB_get_program_binary is not supported!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto& disk_cache = impl->disk_cache;
|
||||
const auto transferable = disk_cache.LoadTransferable();
|
||||
if (!transferable) {
|
||||
|
@ -1,35 +1,44 @@
|
||||
// Copyright 2018 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 <memory>
|
||||
#include <glad/glad.h>
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_gen.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/pica_to_gl.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
class EmuWindow;
|
||||
}
|
||||
|
||||
namespace Pica {
|
||||
struct Regs;
|
||||
struct ShaderRegs;
|
||||
} // namespace Pica
|
||||
|
||||
namespace Pica::Shader {
|
||||
struct ShaderSetup;
|
||||
}
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
enum class UniformBindings : GLuint { Common, VS, GS };
|
||||
enum class UniformBindings : u32 { Common, VS, GS };
|
||||
|
||||
struct LightSrc {
|
||||
alignas(16) GLvec3 specular_0;
|
||||
alignas(16) GLvec3 specular_1;
|
||||
alignas(16) GLvec3 diffuse;
|
||||
alignas(16) GLvec3 ambient;
|
||||
alignas(16) GLvec3 position;
|
||||
alignas(16) GLvec3 spot_direction; // negated
|
||||
GLfloat dist_atten_bias;
|
||||
GLfloat dist_atten_scale;
|
||||
alignas(16) Common::Vec3f specular_0;
|
||||
alignas(16) Common::Vec3f specular_1;
|
||||
alignas(16) Common::Vec3f diffuse;
|
||||
alignas(16) Common::Vec3f ambient;
|
||||
alignas(16) Common::Vec3f position;
|
||||
alignas(16) Common::Vec3f spot_direction; // negated
|
||||
float dist_atten_bias;
|
||||
float dist_atten_scale;
|
||||
};
|
||||
|
||||
/// Uniform structure for the Uniform Buffer Object, all vectors must be 16-byte aligned
|
||||
@ -37,39 +46,38 @@ struct LightSrc {
|
||||
// the end of a uniform block is included in UNIFORM_BLOCK_DATA_SIZE or not.
|
||||
// Not following that rule will cause problems on some AMD drivers.
|
||||
struct UniformData {
|
||||
GLint framebuffer_scale;
|
||||
GLint alphatest_ref;
|
||||
GLfloat depth_scale;
|
||||
GLfloat depth_offset;
|
||||
GLfloat shadow_bias_constant;
|
||||
GLfloat shadow_bias_linear;
|
||||
GLint scissor_x1;
|
||||
GLint scissor_y1;
|
||||
GLint scissor_x2;
|
||||
GLint scissor_y2;
|
||||
GLint fog_lut_offset;
|
||||
GLint proctex_noise_lut_offset;
|
||||
GLint proctex_color_map_offset;
|
||||
GLint proctex_alpha_map_offset;
|
||||
GLint proctex_lut_offset;
|
||||
GLint proctex_diff_lut_offset;
|
||||
GLfloat proctex_bias;
|
||||
GLint shadow_texture_bias;
|
||||
alignas(16) GLivec4 lighting_lut_offset[Pica::LightingRegs::NumLightingSampler / 4];
|
||||
alignas(16) GLvec3 fog_color;
|
||||
alignas(8) GLvec2 proctex_noise_f;
|
||||
alignas(8) GLvec2 proctex_noise_a;
|
||||
alignas(8) GLvec2 proctex_noise_p;
|
||||
alignas(16) GLvec3 lighting_global_ambient;
|
||||
int framebuffer_scale;
|
||||
int alphatest_ref;
|
||||
float depth_scale;
|
||||
float depth_offset;
|
||||
float shadow_bias_constant;
|
||||
float shadow_bias_linear;
|
||||
int scissor_x1;
|
||||
int scissor_y1;
|
||||
int scissor_x2;
|
||||
int scissor_y2;
|
||||
int fog_lut_offset;
|
||||
int proctex_noise_lut_offset;
|
||||
int proctex_color_map_offset;
|
||||
int proctex_alpha_map_offset;
|
||||
int proctex_lut_offset;
|
||||
int proctex_diff_lut_offset;
|
||||
float proctex_bias;
|
||||
int shadow_texture_bias;
|
||||
alignas(16) Common::Vec4i lighting_lut_offset[Pica::LightingRegs::NumLightingSampler / 4];
|
||||
alignas(16) Common::Vec3f fog_color;
|
||||
alignas(8) Common::Vec2f proctex_noise_f;
|
||||
alignas(8) Common::Vec2f proctex_noise_a;
|
||||
alignas(8) Common::Vec2f proctex_noise_p;
|
||||
alignas(16) Common::Vec3f lighting_global_ambient;
|
||||
LightSrc light_src[8];
|
||||
alignas(16) GLvec4 const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) GLvec4 tev_combiner_buffer_color;
|
||||
alignas(16) GLvec4 clip_coef;
|
||||
alignas(16) Common::Vec4f const_color[6]; // A vec4 color for each of the six tev stages
|
||||
alignas(16) Common::Vec4f tev_combiner_buffer_color;
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
};
|
||||
|
||||
static_assert(
|
||||
sizeof(UniformData) == 0x4F0,
|
||||
"The size of the UniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(UniformData) == 0x4F0,
|
||||
"The size of the UniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(UniformData) < 16384,
|
||||
"UniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
@ -79,27 +87,28 @@ struct PicaUniformsData {
|
||||
void SetFromRegs(const Pica::ShaderRegs& regs, const Pica::Shader::ShaderSetup& setup);
|
||||
|
||||
struct BoolAligned {
|
||||
alignas(16) GLint b;
|
||||
alignas(16) int b;
|
||||
};
|
||||
|
||||
std::array<BoolAligned, 16> bools;
|
||||
alignas(16) std::array<GLuvec4, 4> i;
|
||||
alignas(16) std::array<GLvec4, 96> f;
|
||||
alignas(16) std::array<Common::Vec4u, 4> i;
|
||||
alignas(16) std::array<Common::Vec4f, 96> f;
|
||||
};
|
||||
|
||||
struct VSUniformData {
|
||||
PicaUniformsData uniforms;
|
||||
};
|
||||
static_assert(
|
||||
sizeof(VSUniformData) == 1856,
|
||||
"The size of the VSUniformData structure has changed, update the structure in the shader");
|
||||
static_assert(sizeof(VSUniformData) == 1856,
|
||||
"The size of the VSUniformData does not match the structure in the shader");
|
||||
static_assert(sizeof(VSUniformData) < 16384,
|
||||
"VSUniformData structure must be less than 16kb as per the OpenGL spec");
|
||||
|
||||
class OpenGLState;
|
||||
|
||||
/// A class that manage different shader stages and configures them with given config data.
|
||||
class ShaderProgramManager {
|
||||
public:
|
||||
ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable, bool is_amd);
|
||||
ShaderProgramManager(Frontend::EmuWindow& emu_window_, bool separable);
|
||||
~ShaderProgramManager();
|
||||
|
||||
void LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||
|
@ -26,7 +26,7 @@ GLuint LoadShader(const char* source, GLenum type) {
|
||||
#extension GL_EXT_clip_cull_distance : enable
|
||||
#endif // defined(GL_EXT_clip_cull_distance)
|
||||
)"
|
||||
: "#version 330\n";
|
||||
: "#version 430 core\n";
|
||||
|
||||
const char* debug_type;
|
||||
switch (type) {
|
||||
|
@ -1,13 +1,10 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_stream_buffer.h"
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
|
||||
@ -15,21 +12,12 @@ MICROPROFILE_DEFINE(OpenGL_StreamBuffer, "OpenGL", "Stream Buffer Orphaning",
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool array_buffer_for_amd,
|
||||
bool prefer_coherent)
|
||||
OGLStreamBuffer::OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent)
|
||||
: gl_target(target), buffer_size(size) {
|
||||
gl_buffer.Create();
|
||||
glBindBuffer(gl_target, gl_buffer.handle);
|
||||
|
||||
GLsizeiptr allocate_size = size;
|
||||
if (array_buffer_for_amd) {
|
||||
// On AMD GPU there is a strange crash in indexed drawing. The crash happens when the buffer
|
||||
// read position is near the end and is an out-of-bound access to the vertex buffer. This is
|
||||
// probably a bug in the driver and is related to the usage of vec3<byte> attributes in the
|
||||
// vertex array. Doubling the allocation size for the vertex buffer seems to avoid the
|
||||
// crash.
|
||||
allocate_size *= 2;
|
||||
}
|
||||
|
||||
if (GLAD_GL_ARB_buffer_storage) {
|
||||
persistent = true;
|
||||
|
@ -1,20 +1,16 @@
|
||||
// Copyright 2018 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 <tuple>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class OGLStreamBuffer : private NonCopyable {
|
||||
public:
|
||||
explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool array_buffer_for_amd,
|
||||
bool prefer_coherent = false);
|
||||
explicit OGLStreamBuffer(GLenum target, GLsizeiptr size, bool prefer_coherent = false);
|
||||
~OGLStreamBuffer();
|
||||
|
||||
GLuint GetHandle() const;
|
||||
|
@ -1,270 +0,0 @@
|
||||
// Copyright 2020 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <climits>
|
||||
#include <boost/icl/interval.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
|
||||
struct SurfaceParams {
|
||||
private:
|
||||
static constexpr std::array<unsigned int, 18> BPP_TABLE = {
|
||||
32, // RGBA8
|
||||
24, // RGB8
|
||||
16, // RGB5A1
|
||||
16, // RGB565
|
||||
16, // RGBA4
|
||||
16, // IA8
|
||||
16, // RG8
|
||||
8, // I8
|
||||
8, // A8
|
||||
8, // IA4
|
||||
4, // I4
|
||||
4, // A4
|
||||
4, // ETC1
|
||||
8, // ETC1A4
|
||||
16, // D16
|
||||
0,
|
||||
24, // D24
|
||||
32, // D24S8
|
||||
};
|
||||
|
||||
public:
|
||||
enum class PixelFormat {
|
||||
// First 5 formats are shared between textures and color buffers
|
||||
RGBA8 = 0,
|
||||
RGB8 = 1,
|
||||
RGB5A1 = 2,
|
||||
RGB565 = 3,
|
||||
RGBA4 = 4,
|
||||
|
||||
// Texture-only formats
|
||||
IA8 = 5,
|
||||
RG8 = 6,
|
||||
I8 = 7,
|
||||
A8 = 8,
|
||||
IA4 = 9,
|
||||
I4 = 10,
|
||||
A4 = 11,
|
||||
ETC1 = 12,
|
||||
ETC1A4 = 13,
|
||||
|
||||
// Depth buffer-only formats
|
||||
D16 = 14,
|
||||
// gap
|
||||
D24 = 16,
|
||||
D24S8 = 17,
|
||||
|
||||
Invalid = 255,
|
||||
};
|
||||
|
||||
enum class SurfaceType {
|
||||
Color = 0,
|
||||
Texture = 1,
|
||||
Depth = 2,
|
||||
DepthStencil = 3,
|
||||
Fill = 4,
|
||||
Invalid = 5
|
||||
};
|
||||
|
||||
static constexpr unsigned int GetFormatBpp(PixelFormat format) {
|
||||
const auto format_idx = static_cast<std::size_t>(format);
|
||||
DEBUG_ASSERT_MSG(format_idx < BPP_TABLE.size(), "Invalid pixel format {}", format_idx);
|
||||
return BPP_TABLE[format_idx];
|
||||
}
|
||||
|
||||
unsigned int GetFormatBpp() const {
|
||||
return GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
static std::string_view PixelFormatAsString(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::RGBA8:
|
||||
return "RGBA8";
|
||||
case PixelFormat::RGB8:
|
||||
return "RGB8";
|
||||
case PixelFormat::RGB5A1:
|
||||
return "RGB5A1";
|
||||
case PixelFormat::RGB565:
|
||||
return "RGB565";
|
||||
case PixelFormat::RGBA4:
|
||||
return "RGBA4";
|
||||
case PixelFormat::IA8:
|
||||
return "IA8";
|
||||
case PixelFormat::RG8:
|
||||
return "RG8";
|
||||
case PixelFormat::I8:
|
||||
return "I8";
|
||||
case PixelFormat::A8:
|
||||
return "A8";
|
||||
case PixelFormat::IA4:
|
||||
return "IA4";
|
||||
case PixelFormat::I4:
|
||||
return "I4";
|
||||
case PixelFormat::A4:
|
||||
return "A4";
|
||||
case PixelFormat::ETC1:
|
||||
return "ETC1";
|
||||
case PixelFormat::ETC1A4:
|
||||
return "ETC1A4";
|
||||
case PixelFormat::D16:
|
||||
return "D16";
|
||||
case PixelFormat::D24:
|
||||
return "D24";
|
||||
case PixelFormat::D24S8:
|
||||
return "D24S8";
|
||||
default:
|
||||
return "Not a real pixel format";
|
||||
}
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
|
||||
return ((unsigned int)format < 14) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
|
||||
return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
|
||||
return ((unsigned int)format < 4) ? (PixelFormat)((unsigned int)format + 14)
|
||||
: PixelFormat::Invalid;
|
||||
}
|
||||
|
||||
static PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
|
||||
switch (format) {
|
||||
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
|
||||
case GPU::Regs::PixelFormat::RGB565:
|
||||
return PixelFormat::RGB565;
|
||||
case GPU::Regs::PixelFormat::RGB5A1:
|
||||
return PixelFormat::RGB5A1;
|
||||
default:
|
||||
return ((unsigned int)format < 5) ? (PixelFormat)format : PixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
static bool CheckFormatsBlittable(PixelFormat pixel_format_a, PixelFormat pixel_format_b) {
|
||||
SurfaceType a_type = GetFormatType(pixel_format_a);
|
||||
SurfaceType b_type = GetFormatType(pixel_format_b);
|
||||
|
||||
if ((a_type == SurfaceType::Color || a_type == SurfaceType::Texture) &&
|
||||
(b_type == SurfaceType::Color || b_type == SurfaceType::Texture)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a_type == SurfaceType::Depth && b_type == SurfaceType::Depth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a_type == SurfaceType::DepthStencil && b_type == SurfaceType::DepthStencil) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
|
||||
if ((unsigned int)pixel_format < 5) {
|
||||
return SurfaceType::Color;
|
||||
}
|
||||
|
||||
if ((unsigned int)pixel_format < 14) {
|
||||
return SurfaceType::Texture;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
|
||||
return SurfaceType::Depth;
|
||||
}
|
||||
|
||||
if (pixel_format == PixelFormat::D24S8) {
|
||||
return SurfaceType::DepthStencil;
|
||||
}
|
||||
|
||||
return SurfaceType::Invalid;
|
||||
}
|
||||
|
||||
/// Update the params "size", "end" and "type" from the already set "addr", "width", "height"
|
||||
/// and "pixel_format"
|
||||
void UpdateParams() {
|
||||
if (stride == 0) {
|
||||
stride = width;
|
||||
}
|
||||
type = GetFormatType(pixel_format);
|
||||
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
|
||||
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
|
||||
end = addr + size;
|
||||
}
|
||||
|
||||
SurfaceInterval GetInterval() const {
|
||||
return SurfaceInterval(addr, end);
|
||||
}
|
||||
|
||||
// Returns the outer rectangle containing "interval"
|
||||
SurfaceParams FromInterval(SurfaceInterval interval) const;
|
||||
|
||||
SurfaceInterval GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const;
|
||||
|
||||
// Returns the region of the biggest valid rectange within interval
|
||||
SurfaceInterval GetCopyableInterval(const Surface& src_surface) const;
|
||||
|
||||
u32 GetScaledWidth() const {
|
||||
return width * res_scale;
|
||||
}
|
||||
|
||||
u32 GetScaledHeight() const {
|
||||
return height * res_scale;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetRect() const {
|
||||
return {0, height, width, 0};
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> GetScaledRect() const {
|
||||
return {0, GetScaledHeight(), GetScaledWidth(), 0};
|
||||
}
|
||||
|
||||
u32 PixelsInBytes(u32 size) const {
|
||||
return size * CHAR_BIT / GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
u32 BytesInPixels(u32 pixels) const {
|
||||
return pixels * GetFormatBpp(pixel_format) / CHAR_BIT;
|
||||
}
|
||||
|
||||
bool ExactMatch(const SurfaceParams& other_surface) const;
|
||||
bool CanSubRect(const SurfaceParams& sub_surface) const;
|
||||
bool CanExpand(const SurfaceParams& expanded_surface) const;
|
||||
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
|
||||
|
||||
Common::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
|
||||
Common::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
|
||||
|
||||
PAddr addr = 0;
|
||||
PAddr end = 0;
|
||||
u32 size = 0;
|
||||
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 stride = 0;
|
||||
u16 res_scale = 1;
|
||||
|
||||
bool is_tiled = false;
|
||||
PixelFormat pixel_format = PixelFormat::Invalid;
|
||||
SurfaceType type = SurfaceType::Invalid;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
@ -1,32 +1,18 @@
|
||||
// Copyright 2015 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 <array>
|
||||
#include <cstddef>
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
using GLvec2 = std::array<GLfloat, 2>;
|
||||
using GLvec3 = std::array<GLfloat, 3>;
|
||||
using GLvec4 = std::array<GLfloat, 4>;
|
||||
|
||||
using GLuvec2 = std::array<GLuint, 2>;
|
||||
using GLuvec3 = std::array<GLuint, 3>;
|
||||
using GLuvec4 = std::array<GLuint, 4>;
|
||||
|
||||
using GLivec2 = std::array<GLint, 2>;
|
||||
using GLivec3 = std::array<GLint, 3>;
|
||||
using GLivec4 = std::array<GLint, 4>;
|
||||
|
||||
namespace PicaToGL {
|
||||
|
||||
using TextureFilter = Pica::TexturingRegs::TextureConfig::TextureFilter;
|
||||
@ -245,21 +231,14 @@ inline GLenum StencilOp(Pica::FramebufferRegs::StencilAction action) {
|
||||
return stencil_op_table[index];
|
||||
}
|
||||
|
||||
inline GLvec4 ColorRGBA8(const u32 color) {
|
||||
return {{
|
||||
(color >> 0 & 0xFF) / 255.0f,
|
||||
(color >> 8 & 0xFF) / 255.0f,
|
||||
(color >> 16 & 0xFF) / 255.0f,
|
||||
(color >> 24 & 0xFF) / 255.0f,
|
||||
}};
|
||||
inline Common::Vec4f ColorRGBA8(const u32 color) {
|
||||
const auto rgba =
|
||||
Common::Vec4u{color >> 0 & 0xFF, color >> 8 & 0xFF, color >> 16 & 0xFF, color >> 24 & 0xFF};
|
||||
return rgba / 255.0f;
|
||||
}
|
||||
|
||||
inline std::array<GLfloat, 3> LightColor(const Pica::LightingRegs::LightColor& color) {
|
||||
return {{
|
||||
color.r / 255.0f,
|
||||
color.g / 255.0f,
|
||||
color.b / 255.0f,
|
||||
}};
|
||||
inline Common::Vec3f LightColor(const Pica::LightingRegs::LightColor& color) {
|
||||
return Common::Vec3u{color.r, color.g, color.b} / 255.0f;
|
||||
}
|
||||
|
||||
} // namespace PicaToGL
|
||||
|
@ -1,27 +1,14 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <glad/glad.h>
|
||||
#include <queue>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/dumping/backend.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hw/lcd.h"
|
||||
#include "core/memory.h"
|
||||
@ -29,6 +16,7 @@
|
||||
#include "core/tracer/recorder.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/post_processing_opengl.h"
|
||||
@ -526,7 +514,6 @@ void RendererOpenGL::RenderToMailbox(const Layout::FramebufferLayout& layout,
|
||||
mailbox->ReloadRenderFrame(frame, layout.width, layout.height);
|
||||
}
|
||||
|
||||
GLuint render_texture = frame->color.handle;
|
||||
state.draw.draw_framebuffer = frame->render.handle;
|
||||
state.Apply();
|
||||
DrawScreens(layout, flipped);
|
||||
@ -1200,6 +1187,8 @@ static const char* GetSource(GLenum source) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
#undef RET
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static const char* GetType(GLenum type) {
|
||||
@ -1218,6 +1207,8 @@ static const char* GetType(GLenum type) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
#undef RET
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity,
|
||||
@ -1243,7 +1234,7 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum
|
||||
VideoCore::ResultStatus RendererOpenGL::Init() {
|
||||
#ifndef ANDROID
|
||||
if (!gladLoadGL()) {
|
||||
return VideoCore::ResultStatus::ErrorBelowGL33;
|
||||
return VideoCore::ResultStatus::ErrorBelowGL43;
|
||||
}
|
||||
|
||||
// Qualcomm has some spammy info messages that are marked as errors but not important
|
||||
@ -1254,9 +1245,9 @@ VideoCore::ResultStatus RendererOpenGL::Init() {
|
||||
}
|
||||
#endif
|
||||
|
||||
const char* gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
|
||||
const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
|
||||
const std::string_view gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
|
||||
const std::string_view gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
|
||||
const std::string_view gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
|
||||
|
||||
LOG_INFO(Render_OpenGL, "GL_VERSION: {}", gl_version);
|
||||
LOG_INFO(Render_OpenGL, "GL_VENDOR: {}", gpu_vendor);
|
||||
@ -1268,12 +1259,12 @@ VideoCore::ResultStatus RendererOpenGL::Init() {
|
||||
telemetry_session.AddField(user_system, "GPU_Model", std::string(gpu_model));
|
||||
telemetry_session.AddField(user_system, "GPU_OpenGL_Version", std::string(gl_version));
|
||||
|
||||
if (!strcmp(gpu_vendor, "GDI Generic")) {
|
||||
if (gpu_vendor == "GDI Generic") {
|
||||
return VideoCore::ResultStatus::ErrorGenericDrivers;
|
||||
}
|
||||
|
||||
if (!(GLAD_GL_VERSION_3_3 || GLAD_GL_ES_VERSION_3_1)) {
|
||||
return VideoCore::ResultStatus::ErrorBelowGL33;
|
||||
if (!(GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1)) {
|
||||
return VideoCore::ResultStatus::ErrorBelowGL43;
|
||||
}
|
||||
|
||||
InitOpenGLObjects();
|
||||
|
@ -1,13 +1,10 @@
|
||||
// Copyright 2014 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 <array>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_opengl/frame_dumper_opengl.h"
|
||||
|
@ -4,13 +4,10 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/texture_downloader_es.h"
|
||||
|
||||
#include "shaders/depth_to_color.frag"
|
||||
@ -78,13 +75,13 @@ void TextureDownloaderES::Test() {
|
||||
}
|
||||
};
|
||||
LOG_INFO(Render_OpenGL, "GL_DEPTH24_STENCIL8 download test starting");
|
||||
test(depth_format_tuples[3], std::vector<u32>{}, 4096,
|
||||
test(GetFormatTuple(PixelFormat::D24S8), std::vector<u32>{}, 4096,
|
||||
[](std::size_t idx) { return static_cast<u32>((idx << 8) | (idx & 0xFF)); });
|
||||
LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT24 download test starting");
|
||||
test(depth_format_tuples[2], std::vector<u32>{}, 4096,
|
||||
test(GetFormatTuple(PixelFormat::D24), std::vector<u32>{}, 4096,
|
||||
[](std::size_t idx) { return static_cast<u32>(idx << 8); });
|
||||
LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT16 download test starting");
|
||||
test(depth_format_tuples[0], std::vector<u16>{}, 256,
|
||||
test(GetFormatTuple(PixelFormat::D16), std::vector<u16>{}, 256,
|
||||
[](std::size_t idx) { return static_cast<u16>(idx); });
|
||||
|
||||
cur_state.Apply();
|
||||
|
@ -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/renderer_opengl/gl_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
|
||||
@ -92,15 +90,9 @@ void Anime4kUltrafast::Filter(GLuint src_tex, const Common::Rectangle<u32>& src_
|
||||
state.Apply();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, texture.tex.handle);
|
||||
if (GL_ARB_texture_storage) {
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, internal_format,
|
||||
src_rect.GetWidth() * internal_scale_factor,
|
||||
src_rect.GetHeight() * internal_scale_factor);
|
||||
} else {
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D, 0, internal_format, src_rect.GetWidth() * internal_scale_factor,
|
||||
src_rect.GetHeight() * internal_scale_factor, 0, format, GL_HALF_FLOAT, nullptr);
|
||||
}
|
||||
glTexStorage2D(GL_TEXTURE_2D, 1, internal_format,
|
||||
src_rect.GetWidth() * internal_scale_factor,
|
||||
src_rect.GetHeight() * internal_scale_factor);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
|
||||
texture.tex.handle, 0);
|
||||
return texture;
|
||||
@ -112,7 +104,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 +123,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/renderer_opengl/gl_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/renderer_opengl/gl_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,32 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/renderer_opengl/gl_surface_params.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,15 +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,
|
||||
SurfaceParams::SurfaceType type, GLuint read_fb_handle,
|
||||
GLuint draw_fb_handle) {
|
||||
// depth / stencil texture filtering is not supported for now
|
||||
if (IsNull() ||
|
||||
(type != SurfaceParams::SurfaceType::Color && type != SurfaceParams::SurfaceType::Texture))
|
||||
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;
|
||||
}
|
||||
|
||||
@ -82,6 +83,7 @@ std::vector<std::string_view> TextureFilterer::GetFilterNames() {
|
||||
return lhs_is_none && !rhs_is_none;
|
||||
return lhs < rhs;
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,7 @@
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/renderer_opengl/gl_surface_params.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filter_base.h"
|
||||
|
||||
namespace OpenGL {
|
||||
@ -19,15 +16,18 @@ 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, SurfaceParams::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/renderer_opengl/gl_rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/xbrz/xbrz_freescale.h"
|
||||
|
||||
#include "shaders/xbrz_freescale.frag"
|
||||
@ -49,6 +48,7 @@
|
||||
namespace OpenGL {
|
||||
|
||||
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 +62,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 +72,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{};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include "core/frontend/emu_window.h"
|
||||
@ -49,7 +50,7 @@ extern Memory::MemorySystem* g_memory;
|
||||
enum class ResultStatus {
|
||||
Success,
|
||||
ErrorGenericDrivers,
|
||||
ErrorBelowGL33,
|
||||
ErrorBelowGL43,
|
||||
};
|
||||
|
||||
/// Initialize the video core
|
||||
|
Loading…
Reference in New Issue
Block a user