rasterizer_cache: Make into template
* This is the final step, now RasterizerCache is compltely decoupled from OpenGL (technically not yet, but that's talking details). For now texture filtering and some GLES paths have been disabled and will be reimplemented in the following commits
This commit is contained in:
@ -26,19 +26,16 @@ 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/surface_base.h
|
||||
rasterizer_cache/types.h
|
||||
rasterizer_cache/utils.cpp
|
||||
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_driver.cpp
|
||||
@ -61,6 +58,8 @@ 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_texture_runtime.cpp
|
||||
renderer_opengl/gl_texture_runtime.h
|
||||
renderer_opengl/gl_vars.cpp
|
||||
renderer_opengl/gl_vars.h
|
||||
renderer_opengl/pica_to_gl.h
|
||||
|
@ -1,218 +0,0 @@
|
||||
// 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 "video_core/rasterizer_cache/cached_surface.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/renderer_opengl/texture_downloader_es.h"
|
||||
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
CachedSurface::~CachedSurface() {
|
||||
if (texture.handle) {
|
||||
const auto tag = HostTextureTag{pixel_format, GetScaledWidth(), GetScaledHeight()};
|
||||
owner.host_texture_recycler.emplace(tag, std::move(texture));
|
||||
}
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_TextureUL, "RasterizerCache", "Texture Upload", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::UploadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging) {
|
||||
MICROPROFILE_SCOPE(RasterizerCache_TextureUL);
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
|
||||
unscaled_tex = owner.AllocateSurfaceTexture(pixel_format, 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();
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(pixel_format);
|
||||
|
||||
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
|
||||
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, staging.buffer.handle);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
reinterpret_cast<void*>(buffer_offset));
|
||||
|
||||
staging.Lock();
|
||||
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
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> from_rect{0, rect.GetHeight(), rect.GetWidth(), 0};
|
||||
if (!owner.texture_filterer->Filter(unscaled_tex, from_rect, texture, scaled_rect, type)) {
|
||||
const TextureBlit texture_blit = {
|
||||
.surface_type = type,
|
||||
.src_level = 0,
|
||||
.dst_level = 0,
|
||||
.src_region = Region2D{
|
||||
.start = {0, 0},
|
||||
.end = {width, height}
|
||||
},
|
||||
.dst_region = Region2D{
|
||||
.start = {rect.left, rect.bottom},
|
||||
.end = {rect.right, rect.top}
|
||||
}
|
||||
};
|
||||
|
||||
runtime.BlitTextures(unscaled_tex, texture, texture_blit);
|
||||
}
|
||||
}
|
||||
|
||||
InvalidateAllWatcher();
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_TextureDL, "RasterizerCache", "Texture Download", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::DownloadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging) {
|
||||
MICROPROFILE_SCOPE(RasterizerCache_TextureDL);
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
OpenGLState prev_state = state;
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
// Ensure no bad interactions with GL_PACK_ALIGNMENT
|
||||
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, staging.buffer.handle);
|
||||
const u32 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
|
||||
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;
|
||||
|
||||
OGLTexture unscaled_tex = owner.AllocateSurfaceTexture(pixel_format, rect.GetWidth(), rect.GetHeight());
|
||||
|
||||
const TextureBlit texture_blit = {
|
||||
.surface_type = type,
|
||||
.src_level = 0,
|
||||
.dst_level = 0,
|
||||
.src_region = Region2D{
|
||||
.start = {scaled_rect.left, scaled_rect.bottom},
|
||||
.end = {scaled_rect.right, scaled_rect.top}
|
||||
},
|
||||
.dst_region = Region2D{
|
||||
.start = {0, 0},
|
||||
.end = {rect.GetWidth(), rect.GetHeight()}
|
||||
}
|
||||
};
|
||||
|
||||
// Blit scaled texture to the unscaled one
|
||||
runtime.BlitTextures(texture, unscaled_tex, texture_blit);
|
||||
|
||||
state.texture_units[0].texture_2d = unscaled_tex.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(pixel_format);
|
||||
if (GLES) {
|
||||
owner.texture_downloader_es->GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type,
|
||||
rect.GetHeight(), rect.GetWidth(),
|
||||
reinterpret_cast<void*>(buffer_offset));
|
||||
} else {
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, reinterpret_cast<void*>(buffer_offset));
|
||||
}
|
||||
} else {
|
||||
const u32 download_size = width * height * GetBytesPerPixel(pixel_format);
|
||||
const BufferTextureCopy texture_download = {
|
||||
.buffer_offset = buffer_offset,
|
||||
.buffer_size = download_size,
|
||||
.buffer_row_length = stride,
|
||||
.buffer_height = height,
|
||||
.surface_type = type,
|
||||
.texture_level = 0,
|
||||
.texture_offset = {rect.bottom, rect.left},
|
||||
.texture_extent = {rect.GetWidth(), rect.GetHeight()}
|
||||
};
|
||||
|
||||
runtime.ReadTexture(texture, texture_download, pixel_format, staging.mapped);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
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
|
@ -1,127 +0,0 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include "common/assert.h"
|
||||
#include "core/custom_tex_cache.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
#include "video_core/rasterizer_cache/texture_runtime.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
|
||||
/**
|
||||
* 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 RasterizerCache;
|
||||
class StagingBuffer;
|
||||
|
||||
class CachedSurface : public SurfaceParams, public std::enable_shared_from_this<CachedSurface> {
|
||||
public:
|
||||
CachedSurface(SurfaceParams params, RasterizerCache& owner, TextureRuntime& runtime)
|
||||
: SurfaceParams(params), owner(owner), runtime(runtime) {}
|
||||
~CachedSurface();
|
||||
|
||||
/// Upload/Download data in gl_buffer in/to this surface's texture
|
||||
void UploadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging);
|
||||
void DownloadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging);
|
||||
|
||||
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[watcher_count++] = 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 = {};
|
||||
watcher_count = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
bool registered = false;
|
||||
SurfaceRegions invalid_regions;
|
||||
|
||||
// Number of bytes to read from fill_data
|
||||
u32 fill_size = 0;
|
||||
std::array<u8, 4> fill_data;
|
||||
OGLTexture texture;
|
||||
|
||||
std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
|
||||
u32 max_level = 0;
|
||||
|
||||
private:
|
||||
RasterizerCache& owner;
|
||||
TextureRuntime& runtime;
|
||||
u32 watcher_count = 0;
|
||||
std::array<std::weak_ptr<SurfaceWatcher>, 8> 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
|
@ -9,11 +9,10 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/color.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/texture/etc1.h"
|
||||
#include "video_core/utils.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace VideoCore {
|
||||
|
||||
template <typename T>
|
||||
inline T MakeInt(const std::byte* bytes) {
|
||||
@ -46,14 +45,6 @@ inline void DecodePixel(const std::byte* source, std::byte* dest) {
|
||||
const u8 ia4 = static_cast<const u8>(source[0]);
|
||||
std::memset(dest, Color::Convert4To8(ia4 >> 4), 3);
|
||||
dest[3] = std::byte{Color::Convert4To8(ia4 & 0xF)};
|
||||
} else if (format == PixelFormat::RGBA8 && GLES) {
|
||||
const u32 abgr = MakeInt<u32>(source);
|
||||
const u32 rgba = std::byteswap(abgr);
|
||||
std::memcpy(dest, &rgba, sizeof(u32));
|
||||
} else if (format == PixelFormat::RGB8 && GLES) {
|
||||
dest[0] = source[2];
|
||||
dest[1] = source[1];
|
||||
dest[2] = source[0];
|
||||
} else {
|
||||
std::memcpy(dest, source, bytes_per_pixel);
|
||||
}
|
||||
@ -111,13 +102,6 @@ inline void EncodePixel(const std::byte* source, std::byte* dest) {
|
||||
if constexpr (format == PixelFormat::D24S8) {
|
||||
const u32 s8d24 = std::rotr(MakeInt<u32>(source), 8);
|
||||
std::memcpy(dest, &s8d24, sizeof(u32));
|
||||
} else if (format == PixelFormat::RGBA8 && GLES) {
|
||||
const u32 abgr = std::byteswap(MakeInt<u32>(source));
|
||||
std::memcpy(dest, &abgr, sizeof(u32));
|
||||
} else if (format == PixelFormat::RGB8 && GLES) {
|
||||
dest[0] = source[2];
|
||||
dest[1] = source[1];
|
||||
dest[2] = source[0];
|
||||
} else {
|
||||
std::memcpy(dest, source, bytes_per_pixel);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace VideoCore {
|
||||
|
||||
constexpr u32 PIXEL_FORMAT_COUNT = 18;
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
211
src/video_core/rasterizer_cache/surface_base.h
Normal file
211
src/video_core/rasterizer_cache/surface_base.h
Normal file
@ -0,0 +1,211 @@
|
||||
// Copyright 2022 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
|
||||
|
||||
/**
|
||||
* A watcher that notifies whether a cached surface has been changed. This is useful for caching
|
||||
* surface collection objects, including texture cube and mipmap.
|
||||
*/
|
||||
template <class S>
|
||||
class SurfaceWatcher {
|
||||
public:
|
||||
explicit SurfaceWatcher(std::weak_ptr<S>&& 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
|
||||
std::shared_ptr<S> Get() const {
|
||||
return surface.lock();
|
||||
}
|
||||
|
||||
public:
|
||||
std::weak_ptr<S> surface;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
template <class S>
|
||||
class SurfaceBase : public SurfaceParams, public std::enable_shared_from_this<S> {
|
||||
using Watcher = SurfaceWatcher<S>;
|
||||
public:
|
||||
SurfaceBase(SurfaceParams& params) : SurfaceParams{params} {}
|
||||
virtual ~SurfaceBase() = default;
|
||||
|
||||
/// Returns true when this surface can be used to fill the fill_interval of dest_surface
|
||||
bool CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const;
|
||||
|
||||
/// Returns true when copy_interval of dest_surface can be validated by copying from this surface
|
||||
bool CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const;
|
||||
|
||||
/// Returns the region of the biggest valid rectange within interval
|
||||
SurfaceInterval GetCopyableInterval(const SurfaceParams& params) const;
|
||||
|
||||
/// Creates a surface watcher linked to this surface
|
||||
std::shared_ptr<Watcher> CreateWatcher();
|
||||
|
||||
/// Invalidates all watchers linked to this surface
|
||||
void InvalidateAllWatcher();
|
||||
|
||||
/// Removes any linked watchers from this surface
|
||||
void UnlinkAllWatcher();
|
||||
|
||||
/// Returns true when the region denoted by interval is valid
|
||||
bool IsRegionValid(SurfaceInterval interval) const {
|
||||
return (invalid_regions.find(interval) == invalid_regions.end());
|
||||
}
|
||||
|
||||
/// Returns true when the entire surface is invalid
|
||||
bool IsSurfaceFullyInvalid() const {
|
||||
auto interval = GetInterval();
|
||||
return *invalid_regions.equal_range(interval).first == interval;
|
||||
}
|
||||
|
||||
public:
|
||||
bool registered = false;
|
||||
bool is_texture_cube = false;
|
||||
SurfaceRegions invalid_regions;
|
||||
std::array<std::shared_ptr<Watcher>, 7> level_watchers;
|
||||
u32 max_level = 0;
|
||||
std::array<u8, 4> fill_data;
|
||||
u32 fill_size = 0;
|
||||
|
||||
public:
|
||||
u32 watcher_count = 0;
|
||||
std::array<std::weak_ptr<Watcher>, 8> watchers;
|
||||
};
|
||||
|
||||
template <class S>
|
||||
bool SurfaceBase<S>::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;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
bool SurfaceBase<S>::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;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
SurfaceInterval SurfaceBase<S>::GetCopyableInterval(const SurfaceParams& params) const {
|
||||
SurfaceInterval result{};
|
||||
const u32 tile_align = params.BytesInPixels(params.is_tiled ? 8 * 8 : 1);
|
||||
const auto valid_regions = SurfaceRegions{params.GetInterval() & GetInterval()} - invalid_regions;
|
||||
|
||||
for (auto& valid_interval : valid_regions) {
|
||||
const SurfaceInterval aligned_interval{
|
||||
params.addr + Common::AlignUp(boost::icl::first(valid_interval) - params.addr, tile_align),
|
||||
params.addr + Common::AlignDown(boost::icl::last_next(valid_interval) - params.addr, tile_align)
|
||||
};
|
||||
|
||||
if (params.BytesInPixels(tile_align) > boost::icl::length(valid_interval) ||
|
||||
boost::icl::length(aligned_interval) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the rectangle within aligned_interval
|
||||
const u32 stride_bytes = params.BytesInPixels(params.stride) * (params.is_tiled ? 8 : 1);
|
||||
SurfaceInterval rect_interval{
|
||||
params.addr + Common::AlignUp(boost::icl::first(aligned_interval) - params.addr, stride_bytes),
|
||||
params.addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - params.addr, stride_bytes),
|
||||
};
|
||||
|
||||
if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
|
||||
// 1 row
|
||||
rect_interval = aligned_interval;
|
||||
} else if (boost::icl::length(rect_interval) == 0) {
|
||||
// 2 rows that do not make a rectangle, return the larger one
|
||||
const SurfaceInterval row1{boost::icl::first(aligned_interval),
|
||||
boost::icl::first(rect_interval)};
|
||||
const SurfaceInterval row2{boost::icl::first(rect_interval),
|
||||
boost::icl::last_next(aligned_interval)};
|
||||
rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
|
||||
}
|
||||
|
||||
if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
|
||||
result = rect_interval;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
auto SurfaceBase<S>::CreateWatcher() -> std::shared_ptr<Watcher> {
|
||||
S* derived = reinterpret_cast<S*>(this);
|
||||
auto watcher = std::make_shared<Watcher>(std::move(derived->weak_from_this()));
|
||||
watchers[watcher_count++] = watcher;
|
||||
return watcher;
|
||||
}
|
||||
|
||||
template <class S>
|
||||
void SurfaceBase<S>::InvalidateAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class S>
|
||||
void SurfaceBase<S>::UnlinkAllWatcher() {
|
||||
for (const auto& watcher : watchers) {
|
||||
if (auto locked = watcher.lock()) {
|
||||
locked->valid = false;
|
||||
locked->surface.reset();
|
||||
}
|
||||
}
|
||||
|
||||
watchers = {};
|
||||
watcher_count = 0;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
@ -6,7 +6,7 @@
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace VideoCore {
|
||||
|
||||
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
|
||||
SurfaceParams params = *this;
|
||||
@ -64,47 +64,6 @@ SurfaceInterval SurfaceParams::GetSubRectInterval(Common::Rectangle<u32> unscale
|
||||
return {addr + BytesInPixels(pixel_offset), addr + BytesInPixels(pixel_offset + pixels)};
|
||||
}
|
||||
|
||||
SurfaceInterval SurfaceParams::GetCopyableInterval(const Surface& src_surface) const {
|
||||
SurfaceInterval result{};
|
||||
const auto valid_regions =
|
||||
SurfaceRegions(GetInterval() & src_surface->GetInterval()) - src_surface->invalid_regions;
|
||||
for (auto& valid_interval : valid_regions) {
|
||||
const SurfaceInterval aligned_interval{
|
||||
addr + Common::AlignUp(boost::icl::first(valid_interval) - addr,
|
||||
BytesInPixels(is_tiled ? 8 * 8 : 1)),
|
||||
addr + Common::AlignDown(boost::icl::last_next(valid_interval) - addr,
|
||||
BytesInPixels(is_tiled ? 8 * 8 : 1))};
|
||||
|
||||
if (BytesInPixels(is_tiled ? 8 * 8 : 1) > boost::icl::length(valid_interval) ||
|
||||
boost::icl::length(aligned_interval) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the rectangle within aligned_interval
|
||||
const u32 stride_bytes = BytesInPixels(stride) * (is_tiled ? 8 : 1);
|
||||
SurfaceInterval rect_interval{
|
||||
addr + Common::AlignUp(boost::icl::first(aligned_interval) - addr, stride_bytes),
|
||||
addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - addr, stride_bytes),
|
||||
};
|
||||
if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
|
||||
// 1 row
|
||||
rect_interval = aligned_interval;
|
||||
} else if (boost::icl::length(rect_interval) == 0) {
|
||||
// 2 rows that do not make a rectangle, return the larger one
|
||||
const SurfaceInterval row1{boost::icl::first(aligned_interval),
|
||||
boost::icl::first(rect_interval)};
|
||||
const SurfaceInterval row2{boost::icl::first(rect_interval),
|
||||
boost::icl::last_next(aligned_interval)};
|
||||
rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
|
||||
}
|
||||
|
||||
if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
|
||||
result = rect_interval;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Common::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
|
||||
const u32 begin_pixel_index = PixelsInBytes(sub_surface.addr - addr);
|
||||
|
||||
|
@ -11,16 +11,13 @@
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
class CachedSurface;
|
||||
using Surface = std::shared_ptr<CachedSurface>;
|
||||
namespace VideoCore {
|
||||
|
||||
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
|
||||
|
||||
class SurfaceParams {
|
||||
public:
|
||||
// Surface match traits
|
||||
/// Surface match traits
|
||||
bool ExactMatch(const SurfaceParams& other_surface) const;
|
||||
bool CanSubRect(const SurfaceParams& sub_surface) const;
|
||||
bool CanExpand(const SurfaceParams& expanded_surface) const;
|
||||
@ -29,13 +26,10 @@ public:
|
||||
Common::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
|
||||
Common::Rectangle<u32> GetScaledSubRect(const SurfaceParams& sub_surface) const;
|
||||
|
||||
// Returns the outer rectangle containing "interval"
|
||||
/// 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) {
|
||||
@ -57,7 +51,7 @@ public:
|
||||
}
|
||||
|
||||
u32 GetFormatBpp() const {
|
||||
return OpenGL::GetFormatBpp(pixel_format);
|
||||
return VideoCore::GetFormatBpp(pixel_format);
|
||||
}
|
||||
|
||||
u32 GetScaledWidth() const {
|
||||
|
@ -1,261 +0,0 @@
|
||||
// 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/utils.h"
|
||||
#include "video_core/rasterizer_cache/texture_runtime.h"
|
||||
#include "video_core/renderer_opengl/gl_driver.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
GLbitfield MakeBufferMask(SurfaceType type) {
|
||||
switch (type) {
|
||||
case SurfaceType::Color:
|
||||
case SurfaceType::Texture:
|
||||
case SurfaceType::Fill:
|
||||
return GL_COLOR_BUFFER_BIT;
|
||||
case SurfaceType::Depth:
|
||||
return GL_DEPTH_BUFFER_BIT;
|
||||
case SurfaceType::DepthStencil:
|
||||
return GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
return GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
|
||||
TextureRuntime::TextureRuntime(Driver& driver) : driver(driver) {
|
||||
read_fbo.Create();
|
||||
draw_fbo.Create();
|
||||
}
|
||||
|
||||
void TextureRuntime::ReadTexture(OGLTexture& texture, const BufferTextureCopy& copy,
|
||||
PixelFormat format, std::span<std::byte> pixels) {
|
||||
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
OpenGLState state{};
|
||||
state.ResetTexture(texture.handle);
|
||||
state.draw.read_framebuffer = read_fbo.handle;
|
||||
state.Apply();
|
||||
|
||||
switch (copy.surface_type) {
|
||||
case SurfaceType::Color:
|
||||
case SurfaceType::Texture:
|
||||
case SurfaceType::Fill:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle,
|
||||
copy.texture_level);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
break;
|
||||
case SurfaceType::Depth:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture.handle,
|
||||
copy.texture_level);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
break;
|
||||
case SurfaceType::DepthStencil:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, copy.texture_level);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
// TODO: Use PBO here
|
||||
const FormatTuple& tuple = GetFormatTuple(format);
|
||||
glReadPixels(copy.texture_offset.x, copy.texture_offset.y,
|
||||
copy.texture_offset.x + copy.texture_extent.width,
|
||||
copy.texture_offset.y + copy.texture_extent.height,
|
||||
tuple.format, tuple.type, pixels.data() + copy.buffer_offset);
|
||||
}
|
||||
|
||||
bool TextureRuntime::ClearTexture(OGLTexture& texture, const TextureClear& clear, ClearValue value) {
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
// Setup scissor rectangle according to the clear rectangle
|
||||
OpenGLState state{};
|
||||
state.scissor.enabled = true;
|
||||
state.scissor.x = clear.rect.offset.x;
|
||||
state.scissor.y = clear.rect.offset.y;
|
||||
state.scissor.width = clear.rect.extent.width;
|
||||
state.scissor.height = clear.rect.extent.height;
|
||||
state.draw.draw_framebuffer = draw_fbo.handle;
|
||||
state.Apply();
|
||||
|
||||
switch (clear.surface_type) {
|
||||
case SurfaceType::Color:
|
||||
case SurfaceType::Texture:
|
||||
case SurfaceType::Fill:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle,
|
||||
clear.texture_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 SurfaceType::Depth:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture.handle,
|
||||
clear.texture_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 SurfaceType::DepthStencil:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, clear.texture_level);
|
||||
|
||||
state.depth.write_mask = GL_TRUE;
|
||||
state.stencil.write_mask = -1;
|
||||
state.Apply();
|
||||
|
||||
glClearBufferfi(GL_DEPTH_STENCIL, 0, value.depth, value.stencil);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureRuntime::CopyTextures(OGLTexture& source, OGLTexture& dest, const TextureCopy& copy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureRuntime::BlitTextures(OGLTexture& source, OGLTexture& dest, const TextureBlit& blit) {
|
||||
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 = [&blit, &source, &dest](GLenum attachment, u32 src_tex, u32 dst_tex) -> void {
|
||||
const GLenum src_target = source.target == GL_TEXTURE_CUBE_MAP ?
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + blit.src_layer : source.target;
|
||||
const GLenum dst_target = dest.target == GL_TEXTURE_CUBE_MAP ?
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + blit.dst_layer : dest.target;
|
||||
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, attachment, src_target, src_tex, blit.src_level);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, dst_target, dst_tex, blit.dst_level);
|
||||
};
|
||||
|
||||
switch (blit.surface_type) {
|
||||
case SurfaceType::Color:
|
||||
case SurfaceType::Texture:
|
||||
case SurfaceType::Fill:
|
||||
// Bind only color
|
||||
BindAttachment(GL_COLOR_ATTACHMENT0, source.handle, dest.handle);
|
||||
BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, 0, 0);
|
||||
break;
|
||||
case SurfaceType::Depth:
|
||||
// Bind only depth
|
||||
BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
BindAttachment(GL_DEPTH_ATTACHMENT, source.handle, dest.handle);
|
||||
BindAttachment(GL_STENCIL_ATTACHMENT, 0, 0);
|
||||
break;
|
||||
case SurfaceType::DepthStencil:
|
||||
// Bind to combined depth + stencil
|
||||
BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, source.handle, dest.handle);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
// 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 GLbitfield buffer_mask = MakeBufferMask(blit.surface_type);
|
||||
const GLenum filter = buffer_mask == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST;
|
||||
glBlitFramebuffer(blit.src_region.start.x, blit.src_region.start.y,
|
||||
blit.src_region.end.x, blit.src_region.end.y,
|
||||
blit.dst_region.start.x, blit.dst_region.start.y,
|
||||
blit.dst_region.end.x, blit.dst_region.end.y,
|
||||
buffer_mask, filter);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureRuntime::GenerateMipmaps(OGLTexture& texture, u32 max_level) {
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
OpenGLState state{};
|
||||
state.texture_units[0].texture_2d = texture.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
const StagingBuffer& TextureRuntime::FindStaging(u32 size, bool upload) {
|
||||
const GLenum target = upload ? GL_PIXEL_UNPACK_BUFFER : GL_PIXEL_PACK_BUFFER;
|
||||
const GLbitfield access = upload ? GL_MAP_WRITE_BIT : GL_MAP_READ_BIT;
|
||||
auto& search = upload ? upload_buffers : download_buffers;
|
||||
|
||||
// Attempt to find a free buffer that fits the requested data
|
||||
for (auto it = search.lower_bound({.size = size}); it != search.end(); it++) {
|
||||
if (!upload || it->IsFree()) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
OGLBuffer buffer{};
|
||||
buffer.Create();
|
||||
|
||||
glBindBuffer(target, buffer.handle);
|
||||
|
||||
// Allocate a new buffer and map the data to the host
|
||||
std::byte* data = nullptr;
|
||||
if (driver.IsOpenGLES() && driver.HasExtBufferStorage()) {
|
||||
const GLbitfield storage = upload ? GL_MAP_WRITE_BIT : GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT_EXT;
|
||||
glBufferStorageEXT(target, size, nullptr, storage | GL_MAP_PERSISTENT_BIT_EXT |
|
||||
GL_MAP_COHERENT_BIT_EXT);
|
||||
data = reinterpret_cast<std::byte*>(glMapBufferRange(target, 0, size, access | GL_MAP_PERSISTENT_BIT_EXT |
|
||||
GL_MAP_COHERENT_BIT_EXT));
|
||||
} else if (driver.HasArbBufferStorage()) {
|
||||
const GLbitfield storage = upload ? GL_MAP_WRITE_BIT : GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT;
|
||||
glBufferStorage(target, size, nullptr, storage | GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_COHERENT_BIT);
|
||||
data = reinterpret_cast<std::byte*>(glMapBufferRange(target, 0, size, access | GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_COHERENT_BIT));
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
StagingBuffer staging = {
|
||||
.buffer = std::move(buffer),
|
||||
.mapped = std::span{data, size},
|
||||
.size = size
|
||||
};
|
||||
|
||||
const auto& it = search.emplace(std::move(staging));
|
||||
return *it;
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
@ -7,7 +7,7 @@
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
|
||||
namespace OpenGL {
|
||||
namespace VideoCore {
|
||||
|
||||
struct Offset {
|
||||
constexpr auto operator<=>(const Offset&) const noexcept = default;
|
||||
|
@ -13,51 +13,7 @@
|
||||
#include "video_core/renderer_opengl/gl_vars.h"
|
||||
#include "video_core/video_core.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 VideoCore {
|
||||
|
||||
void SwizzleTexture(const SurfaceParams& params, u32 start_offset,
|
||||
std::span<std::byte> source_linear, std::span<std::byte> dest_tiled) {
|
||||
|
@ -9,15 +9,7 @@
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/rasterizer_cache/types.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct FormatTuple {
|
||||
int internal_format;
|
||||
u32 format;
|
||||
u32 type;
|
||||
};
|
||||
|
||||
const FormatTuple& GetFormatTuple(PixelFormat pixel_format);
|
||||
namespace VideoCore {
|
||||
|
||||
struct HostTextureTag {
|
||||
PixelFormat format{};
|
||||
@ -70,15 +62,15 @@ void UnswizzleTexture(const SurfaceParams& params, u32 start_offset,
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<OpenGL::HostTextureTag> {
|
||||
std::size_t operator()(const OpenGL::HostTextureTag& tag) const noexcept {
|
||||
struct hash<VideoCore::HostTextureTag> {
|
||||
std::size_t operator()(const VideoCore::HostTextureTag& tag) const noexcept {
|
||||
return tag.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<OpenGL::TextureCubeConfig> {
|
||||
std::size_t operator()(const OpenGL::TextureCubeConfig& config) const noexcept {
|
||||
struct hash<VideoCore::TextureCubeConfig> {
|
||||
std::size_t operator()(const VideoCore::TextureCubeConfig& config) const noexcept {
|
||||
return config.Hash();
|
||||
}
|
||||
};
|
||||
|
@ -60,8 +60,8 @@ void main() {
|
||||
vao.Create();
|
||||
}
|
||||
|
||||
PixelFormat GetSourceFormat() const override {
|
||||
return PixelFormat::RGBA4;
|
||||
VideoCore::PixelFormat GetSourceFormat() const override {
|
||||
return VideoCore::PixelFormat::RGBA4;
|
||||
}
|
||||
|
||||
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
|
||||
@ -170,8 +170,8 @@ void main() {
|
||||
}
|
||||
}
|
||||
|
||||
PixelFormat GetSourceFormat() const override {
|
||||
return PixelFormat::D24S8;
|
||||
VideoCore::PixelFormat GetSourceFormat() const override {
|
||||
return VideoCore::PixelFormat::D24S8;
|
||||
}
|
||||
|
||||
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
|
||||
@ -246,18 +246,18 @@ 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))};
|
||||
|
||||
auto Register = [this](PixelFormat dest, std::unique_ptr<FormatReinterpreterBase>&& obj) {
|
||||
auto Register = [this](VideoCore::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>());
|
||||
Register(VideoCore::PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
|
||||
LOG_INFO(Render_OpenGL, "Using shader for D24S8 to RGBA8 reinterpretation");
|
||||
|
||||
Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
|
||||
Register(VideoCore::PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
|
||||
}
|
||||
|
||||
auto FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format)
|
||||
auto FormatReinterpreterOpenGL::GetPossibleReinterpretations(VideoCore::PixelFormat dst_format)
|
||||
-> const ReinterpreterList& {
|
||||
return reinterpreters[static_cast<u32>(dst_format)];
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
|
||||
virtual ~FormatReinterpreterBase() = default;
|
||||
|
||||
virtual PixelFormat GetSourceFormat() const = 0;
|
||||
virtual VideoCore::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;
|
||||
|
||||
@ -38,10 +38,10 @@ public:
|
||||
FormatReinterpreterOpenGL();
|
||||
~FormatReinterpreterOpenGL() = default;
|
||||
|
||||
const ReinterpreterList& GetPossibleReinterpretations(PixelFormat dst_format);
|
||||
const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dst_format);
|
||||
|
||||
private:
|
||||
std::array<ReinterpreterList, PIXEL_FORMAT_COUNT> reinterpreters;
|
||||
std::array<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
@ -39,7 +39,8 @@ static bool IsVendorIntel() {
|
||||
#endif
|
||||
|
||||
RasterizerOpenGL::RasterizerOpenGL(Frontend::EmuWindow& emu_window, Driver& driver)
|
||||
: driver(driver), is_amd(IsVendorAmd()), vertex_buffer(GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE, is_amd),
|
||||
: driver{driver}, runtime{driver}, res_cache{*this, runtime},
|
||||
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),
|
||||
@ -526,8 +527,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
regs.rasterizer.viewport_corner.y // bottom
|
||||
};
|
||||
|
||||
Surface color_surface;
|
||||
Surface depth_surface;
|
||||
RasterizerCache::Surface color_surface, depth_surface;
|
||||
Common::Rectangle<u32> surfaces_rect;
|
||||
std::tie(color_surface, depth_surface, surfaces_rect) =
|
||||
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb, viewport_rect_unscaled);
|
||||
@ -638,7 +638,7 @@ 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);
|
||||
auto surface = res_cache.GetTextureSurface(info);
|
||||
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(target = surface->texture.handle);
|
||||
@ -657,7 +657,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||
switch (texture.config.type.Value()) {
|
||||
case TextureType::Shadow2D: {
|
||||
Surface surface = res_cache.GetTextureSurface(texture);
|
||||
auto surface = res_cache.GetTextureSurface(texture);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.image_shadow_texture_px = surface->texture.handle);
|
||||
} else {
|
||||
@ -677,23 +677,26 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
BindCubeFace(state.image_shadow_texture_nz, CubeFace::NegativeZ, info);
|
||||
continue;
|
||||
}
|
||||
case TextureType::TextureCube:
|
||||
case TextureType::TextureCube: {
|
||||
using CubeFace = Pica::TexturingRegs::CubeFace;
|
||||
TextureCubeConfig config;
|
||||
config.px = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX);
|
||||
config.nx = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX);
|
||||
config.py = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY);
|
||||
config.ny = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY);
|
||||
config.pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ);
|
||||
config.nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ);
|
||||
config.width = texture.config.width;
|
||||
config.format = texture.format;
|
||||
const VideoCore::TextureCubeConfig config = {
|
||||
.px = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX),
|
||||
.nx = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeX),
|
||||
.py = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveY),
|
||||
.ny = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeY),
|
||||
.pz = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveZ),
|
||||
.nz = regs.texturing.GetCubePhysicalAddress(CubeFace::NegativeZ),
|
||||
.width = texture.config.width,
|
||||
.format = texture.format
|
||||
};
|
||||
|
||||
state.texture_cube_unit.texture_cube =
|
||||
res_cache.GetTextureCube(config).texture.handle;
|
||||
res_cache.GetTextureCube(config)->texture.handle;
|
||||
|
||||
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;
|
||||
}
|
||||
@ -702,7 +705,7 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
}
|
||||
|
||||
texture_samplers[texture_index].SyncWithConfig(texture.config);
|
||||
Surface surface = res_cache.GetTextureSurface(texture);
|
||||
auto surface = res_cache.GetTextureSurface(texture);
|
||||
if (surface != nullptr) {
|
||||
CheckBarrier(state.texture_units[texture_index].texture_2d =
|
||||
surface->texture.handle);
|
||||
@ -721,19 +724,15 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
OGLTexture temp_tex;
|
||||
if (need_duplicate_texture) {
|
||||
const auto& tuple = GetFormatTuple(color_surface->pixel_format);
|
||||
const GLsizei levels = color_surface->max_level + 1;
|
||||
temp_tex = runtime.Allocate2D(color_surface->GetScaledWidth(), color_surface->GetScaledHeight(),
|
||||
color_surface->pixel_format);
|
||||
|
||||
// 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();
|
||||
temp_tex.Allocate(GL_TEXTURE_2D, levels, tuple.internal_format,
|
||||
color_surface->GetScaledWidth(), color_surface->GetScaledHeight());
|
||||
|
||||
temp_tex.CopyFrom(color_surface->texture, GL_TEXTURE_2D, levels,
|
||||
temp_tex.CopyFrom(color_surface->texture, GL_TEXTURE_2D, color_surface->max_level + 1,
|
||||
color_surface->GetScaledWidth(), color_surface->GetScaledHeight());
|
||||
|
||||
for (auto& unit : state.texture_units) {
|
||||
@ -1364,40 +1363,37 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
|
||||
bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Blits);
|
||||
|
||||
SurfaceParams src_params;
|
||||
VideoCore::SurfaceParams src_params;
|
||||
src_params.addr = config.GetPhysicalInputAddress();
|
||||
src_params.width = config.output_width;
|
||||
src_params.stride = config.input_width;
|
||||
src_params.height = config.output_height;
|
||||
src_params.is_tiled = !config.input_linear;
|
||||
src_params.pixel_format = PixelFormatFromGPUPixelFormat(config.input_format);
|
||||
src_params.pixel_format = VideoCore::PixelFormatFromGPUPixelFormat(config.input_format);
|
||||
src_params.UpdateParams();
|
||||
|
||||
SurfaceParams dst_params;
|
||||
VideoCore::SurfaceParams dst_params;
|
||||
dst_params.addr = config.GetPhysicalOutputAddress();
|
||||
dst_params.width = config.scaling != config.NoScale ? config.output_width.Value() / 2
|
||||
: config.output_width.Value();
|
||||
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 = PixelFormatFromGPUPixelFormat(config.output_format);
|
||||
dst_params.pixel_format = VideoCore::PixelFormatFromGPUPixelFormat(config.output_format);
|
||||
dst_params.UpdateParams();
|
||||
|
||||
Common::Rectangle<u32> src_rect;
|
||||
Surface src_surface;
|
||||
std::tie(src_surface, src_rect) =
|
||||
res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true);
|
||||
auto [src_surface, src_rect] =
|
||||
res_cache.GetSurfaceSubRect(src_params, VideoCore::ScaleMatch::Ignore, true);
|
||||
if (src_surface == nullptr)
|
||||
return false;
|
||||
|
||||
dst_params.res_scale = src_surface->res_scale;
|
||||
|
||||
Common::Rectangle<u32> dst_rect;
|
||||
Surface dst_surface;
|
||||
std::tie(dst_surface, dst_rect) =
|
||||
res_cache.GetSurfaceSubRect(dst_params, ScaleMatch::Upscale, false);
|
||||
if (dst_surface == nullptr)
|
||||
auto [dst_surface, dst_rect] =
|
||||
res_cache.GetSurfaceSubRect(dst_params, VideoCore::ScaleMatch::Upscale, false);
|
||||
if (dst_surface == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (src_surface->is_tiled != dst_surface->is_tiled)
|
||||
std::swap(src_rect.top, src_rect.bottom);
|
||||
@ -1444,7 +1440,7 @@ bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferCon
|
||||
return false;
|
||||
}
|
||||
|
||||
SurfaceParams src_params;
|
||||
VideoCore::SurfaceParams src_params;
|
||||
src_params.addr = config.GetPhysicalInputAddress();
|
||||
src_params.stride = input_width + input_gap; // stride in bytes
|
||||
src_params.width = input_width; // width in bytes
|
||||
@ -1452,9 +1448,7 @@ bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferCon
|
||||
src_params.size = ((src_params.height - 1) * src_params.stride) + src_params.width;
|
||||
src_params.end = src_params.addr + src_params.size;
|
||||
|
||||
Common::Rectangle<u32> src_rect;
|
||||
Surface src_surface;
|
||||
std::tie(src_surface, src_rect) = res_cache.GetTexCopySurface(src_params);
|
||||
auto [src_surface, src_rect] = res_cache.GetTexCopySurface(src_params);
|
||||
if (src_surface == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@ -1466,7 +1460,7 @@ bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferCon
|
||||
return false;
|
||||
}
|
||||
|
||||
SurfaceParams dst_params = *src_surface;
|
||||
VideoCore::SurfaceParams dst_params = *src_surface;
|
||||
dst_params.addr = config.GetPhysicalOutputAddress();
|
||||
dst_params.width = src_rect.GetWidth() / src_surface->res_scale;
|
||||
dst_params.stride = dst_params.width + src_surface->PixelsInBytes(
|
||||
@ -1477,15 +1471,13 @@ bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferCon
|
||||
|
||||
// Since we are going to invalidate the gap if there is one, we will have to load it first
|
||||
const bool load_gap = output_gap != 0;
|
||||
Common::Rectangle<u32> dst_rect;
|
||||
Surface dst_surface;
|
||||
std::tie(dst_surface, dst_rect) =
|
||||
res_cache.GetSurfaceSubRect(dst_params, ScaleMatch::Upscale, load_gap);
|
||||
auto [dst_surface, dst_rect] =
|
||||
res_cache.GetSurfaceSubRect(dst_params, VideoCore::ScaleMatch::Upscale, load_gap);
|
||||
if (dst_surface == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dst_surface->type == SurfaceType::Texture) {
|
||||
if (dst_surface->type == VideoCore::SurfaceType::Texture) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1498,7 +1490,7 @@ bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferCon
|
||||
}
|
||||
|
||||
bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) {
|
||||
Surface dst_surface = res_cache.GetFillSurface(config);
|
||||
auto dst_surface = res_cache.GetFillSurface(config);
|
||||
if (dst_surface == nullptr)
|
||||
return false;
|
||||
|
||||
@ -1514,19 +1506,17 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con
|
||||
}
|
||||
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
|
||||
|
||||
SurfaceParams src_params;
|
||||
VideoCore::SurfaceParams src_params;
|
||||
src_params.addr = framebuffer_addr;
|
||||
src_params.width = std::min(config.width.Value(), pixel_stride);
|
||||
src_params.height = config.height;
|
||||
src_params.stride = pixel_stride;
|
||||
src_params.is_tiled = false;
|
||||
src_params.pixel_format = PixelFormatFromGPUPixelFormat(config.color_format);
|
||||
src_params.pixel_format = VideoCore::PixelFormatFromGPUPixelFormat(config.color_format);
|
||||
src_params.UpdateParams();
|
||||
|
||||
Common::Rectangle<u32> src_rect;
|
||||
Surface src_surface;
|
||||
std::tie(src_surface, src_rect) =
|
||||
res_cache.GetSurfaceSubRect(src_params, ScaleMatch::Ignore, true);
|
||||
const auto [src_surface, src_rect] =
|
||||
res_cache.GetSurfaceSubRect(src_params, VideoCore::ScaleMatch::Ignore, true);
|
||||
|
||||
if (src_surface == nullptr) {
|
||||
return false;
|
||||
|
@ -6,10 +6,10 @@
|
||||
#include "common/vector_math.h"
|
||||
#include "core/hw/gpu.h"
|
||||
#include "video_core/pica_types.h"
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_accelerated.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_texturing.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_runtime.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"
|
||||
@ -252,6 +252,7 @@ private:
|
||||
OpenGLState state;
|
||||
GLuint default_texture;
|
||||
|
||||
TextureRuntime runtime;
|
||||
RasterizerCache res_cache;
|
||||
|
||||
std::vector<HardwareVertex> vertex_batch;
|
||||
|
490
src/video_core/renderer_opengl/gl_texture_runtime.cpp
Normal file
490
src/video_core/renderer_opengl/gl_texture_runtime.cpp
Normal file
@ -0,0 +1,490 @@
|
||||
// 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/utils.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_runtime.h"
|
||||
#include "video_core/renderer_opengl/gl_driver.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
|
||||
|
||||
static constexpr std::array DEPTH_TUPLES = {
|
||||
FormatTuple{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
|
||||
FormatTuple{},
|
||||
FormatTuple{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
|
||||
FormatTuple{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
|
||||
};
|
||||
|
||||
static constexpr std::array COLOR_TUPLES = {
|
||||
FormatTuple{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
|
||||
FormatTuple{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
|
||||
FormatTuple{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
||||
FormatTuple{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
FormatTuple{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
||||
};
|
||||
|
||||
static constexpr std::array COLOR_TUPLES_OES = {
|
||||
FormatTuple{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
FormatTuple{GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, // RGB8
|
||||
FormatTuple{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
|
||||
FormatTuple{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
FormatTuple{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
|
||||
};
|
||||
|
||||
GLbitfield MakeBufferMask(VideoCore::SurfaceType type) {
|
||||
switch (type) {
|
||||
case VideoCore::SurfaceType::Color:
|
||||
case VideoCore::SurfaceType::Texture:
|
||||
case VideoCore::SurfaceType::Fill:
|
||||
return GL_COLOR_BUFFER_BIT;
|
||||
case VideoCore::SurfaceType::Depth:
|
||||
return GL_DEPTH_BUFFER_BIT;
|
||||
case VideoCore::SurfaceType::DepthStencil:
|
||||
return GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
return GL_COLOR_BUFFER_BIT;
|
||||
}
|
||||
|
||||
TextureRuntime::TextureRuntime(Driver& driver) : driver(driver) {
|
||||
read_fbo.Create();
|
||||
draw_fbo.Create();
|
||||
}
|
||||
|
||||
const StagingBuffer& TextureRuntime::FindStaging(u32 size, bool upload) {
|
||||
const GLenum target = upload ? GL_PIXEL_UNPACK_BUFFER : GL_PIXEL_PACK_BUFFER;
|
||||
const GLbitfield access = upload ? GL_MAP_WRITE_BIT : GL_MAP_READ_BIT;
|
||||
auto& search = upload ? upload_buffers : download_buffers;
|
||||
|
||||
// Attempt to find a free buffer that fits the requested data
|
||||
for (auto it = search.lower_bound({.size = size}); it != search.end(); it++) {
|
||||
if (!upload || it->IsFree()) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
OGLBuffer buffer{};
|
||||
buffer.Create();
|
||||
|
||||
glBindBuffer(target, buffer.handle);
|
||||
|
||||
// Allocate a new buffer and map the data to the host
|
||||
std::byte* data = nullptr;
|
||||
if (driver.IsOpenGLES() && driver.HasExtBufferStorage()) {
|
||||
const GLbitfield storage = upload ? GL_MAP_WRITE_BIT : GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT_EXT;
|
||||
glBufferStorageEXT(target, size, nullptr, storage | GL_MAP_PERSISTENT_BIT_EXT |
|
||||
GL_MAP_COHERENT_BIT_EXT);
|
||||
data = reinterpret_cast<std::byte*>(glMapBufferRange(target, 0, size, access | GL_MAP_PERSISTENT_BIT_EXT |
|
||||
GL_MAP_COHERENT_BIT_EXT));
|
||||
} else if (driver.HasArbBufferStorage()) {
|
||||
const GLbitfield storage = upload ? GL_MAP_WRITE_BIT : GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT;
|
||||
glBufferStorage(target, size, nullptr, storage | GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_COHERENT_BIT);
|
||||
data = reinterpret_cast<std::byte*>(glMapBufferRange(target, 0, size, access | GL_MAP_PERSISTENT_BIT |
|
||||
GL_MAP_COHERENT_BIT));
|
||||
} else {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
glBindBuffer(target, 0);
|
||||
|
||||
StagingBuffer staging = {
|
||||
.buffer = std::move(buffer),
|
||||
.mapped = std::span{data, size},
|
||||
.size = size
|
||||
};
|
||||
|
||||
const auto& it = search.emplace(std::move(staging));
|
||||
return *it;
|
||||
}
|
||||
|
||||
const FormatTuple& TextureRuntime::GetFormatTuple(VideoCore::PixelFormat pixel_format) {
|
||||
const auto type = GetFormatType(pixel_format);
|
||||
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
|
||||
|
||||
if (type == VideoCore::SurfaceType::Color) {
|
||||
ASSERT(format_index < COLOR_TUPLES.size());
|
||||
return (driver.IsOpenGLES() ? COLOR_TUPLES_OES : COLOR_TUPLES)[format_index];
|
||||
} else if (type == VideoCore::SurfaceType::Depth ||
|
||||
type == VideoCore::SurfaceType::DepthStencil) {
|
||||
const std::size_t tuple_idx = format_index - 14;
|
||||
ASSERT(tuple_idx < DEPTH_TUPLES.size());
|
||||
return DEPTH_TUPLES[tuple_idx];
|
||||
}
|
||||
|
||||
return DEFAULT_TUPLE;
|
||||
}
|
||||
|
||||
OGLTexture TextureRuntime::Allocate2D(u32 width, u32 height, VideoCore::PixelFormat format) {
|
||||
const auto& tuple = GetFormatTuple(format);
|
||||
auto recycled_tex = texture2d_recycler.find({format, width, height});
|
||||
if (recycled_tex != texture2d_recycler.end()) {
|
||||
OGLTexture texture = std::move(recycled_tex->second);
|
||||
texture2d_recycler.erase(recycled_tex);
|
||||
return texture;
|
||||
}
|
||||
|
||||
// Allocate the 2D texture
|
||||
OGLTexture texture{};
|
||||
texture.Create();
|
||||
texture.Allocate(GL_TEXTURE_2D, std::bit_width(std::max(width, height)),
|
||||
tuple.internal_format, width, height);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
OGLTexture TextureRuntime::AllocateCubeMap(u32 width, VideoCore::PixelFormat format) {
|
||||
const auto& tuple = GetFormatTuple(format);
|
||||
|
||||
// Allocate the cube texture
|
||||
OGLTexture texture{};
|
||||
texture.Create();
|
||||
texture.Allocate(GL_TEXTURE_CUBE_MAP, std::bit_width(width),
|
||||
tuple.internal_format, width, width);
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void TextureRuntime::ReadTexture(OGLTexture& texture, const VideoCore::BufferTextureCopy& copy,
|
||||
VideoCore::PixelFormat format) {
|
||||
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
OpenGLState state{};
|
||||
state.ResetTexture(texture.handle);
|
||||
state.draw.read_framebuffer = read_fbo.handle;
|
||||
state.Apply();
|
||||
|
||||
switch (copy.surface_type) {
|
||||
case VideoCore::SurfaceType::Color:
|
||||
case VideoCore::SurfaceType::Texture:
|
||||
case VideoCore::SurfaceType::Fill:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle,
|
||||
copy.texture_level);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
break;
|
||||
case VideoCore::SurfaceType::Depth:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture.handle,
|
||||
copy.texture_level);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
break;
|
||||
case VideoCore::SurfaceType::DepthStencil:
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, copy.texture_level);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
const FormatTuple& tuple = GetFormatTuple(format);
|
||||
glReadPixels(copy.texture_offset.x, copy.texture_offset.y,
|
||||
copy.texture_offset.x + copy.texture_extent.width,
|
||||
copy.texture_offset.y + copy.texture_extent.height,
|
||||
tuple.format, tuple.type,
|
||||
reinterpret_cast<void*>(copy.buffer_offset));
|
||||
}
|
||||
|
||||
bool TextureRuntime::ClearTexture(OGLTexture& texture, const VideoCore::TextureClear& clear,
|
||||
VideoCore::ClearValue value) {
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
// Setup scissor rectangle according to the clear rectangle
|
||||
OpenGLState state{};
|
||||
state.scissor.enabled = true;
|
||||
state.scissor.x = clear.rect.offset.x;
|
||||
state.scissor.y = clear.rect.offset.y;
|
||||
state.scissor.width = clear.rect.extent.width;
|
||||
state.scissor.height = clear.rect.extent.height;
|
||||
state.draw.draw_framebuffer = draw_fbo.handle;
|
||||
state.Apply();
|
||||
|
||||
switch (clear.surface_type) {
|
||||
case VideoCore::SurfaceType::Color:
|
||||
case VideoCore::SurfaceType::Texture:
|
||||
case VideoCore::SurfaceType::Fill:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.handle,
|
||||
clear.texture_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 VideoCore::SurfaceType::Depth:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture.handle,
|
||||
clear.texture_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 VideoCore::SurfaceType::DepthStencil:
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
|
||||
texture.handle, clear.texture_level);
|
||||
|
||||
state.depth.write_mask = GL_TRUE;
|
||||
state.stencil.write_mask = -1;
|
||||
state.Apply();
|
||||
|
||||
glClearBufferfi(GL_DEPTH_STENCIL, 0, value.depth, value.stencil);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureRuntime::CopyTextures(OGLTexture& source, OGLTexture& dest, const VideoCore::TextureCopy& copy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextureRuntime::BlitTextures(OGLTexture& source, OGLTexture& dest, const VideoCore::TextureBlit& blit) {
|
||||
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 = [&blit, &source, &dest](GLenum attachment, u32 src_tex, u32 dst_tex) -> void {
|
||||
const GLenum src_target = source.target == GL_TEXTURE_CUBE_MAP ?
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + blit.src_layer : source.target;
|
||||
const GLenum dst_target = dest.target == GL_TEXTURE_CUBE_MAP ?
|
||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + blit.dst_layer : dest.target;
|
||||
|
||||
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, attachment, src_target, src_tex, blit.src_level);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment, dst_target, dst_tex, blit.dst_level);
|
||||
};
|
||||
|
||||
switch (blit.surface_type) {
|
||||
case VideoCore::SurfaceType::Color:
|
||||
case VideoCore::SurfaceType::Texture:
|
||||
case VideoCore::SurfaceType::Fill:
|
||||
// Bind only color
|
||||
BindAttachment(GL_COLOR_ATTACHMENT0, source.handle, dest.handle);
|
||||
BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, 0, 0);
|
||||
break;
|
||||
case VideoCore::SurfaceType::Depth:
|
||||
// Bind only depth
|
||||
BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
BindAttachment(GL_DEPTH_ATTACHMENT, source.handle, dest.handle);
|
||||
BindAttachment(GL_STENCIL_ATTACHMENT, 0, 0);
|
||||
break;
|
||||
case VideoCore::SurfaceType::DepthStencil:
|
||||
// Bind to combined depth + stencil
|
||||
BindAttachment(GL_COLOR_ATTACHMENT0, 0, 0);
|
||||
BindAttachment(GL_DEPTH_STENCIL_ATTACHMENT, source.handle, dest.handle);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE_MSG("Invalid surface type!");
|
||||
}
|
||||
|
||||
// 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 GLbitfield buffer_mask = MakeBufferMask(blit.surface_type);
|
||||
const GLenum filter = buffer_mask == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST;
|
||||
glBlitFramebuffer(blit.src_region.start.x, blit.src_region.start.y,
|
||||
blit.src_region.end.x, blit.src_region.end.y,
|
||||
blit.dst_region.start.x, blit.dst_region.start.y,
|
||||
blit.dst_region.end.x, blit.dst_region.end.y,
|
||||
buffer_mask, filter);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void TextureRuntime::GenerateMipmaps(OGLTexture& texture, u32 max_level) {
|
||||
OpenGLState prev_state = OpenGLState::GetCurState();
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
OpenGLState state{};
|
||||
state.texture_units[0].texture_2d = texture.handle;
|
||||
state.Apply();
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level);
|
||||
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_TextureUL, "RasterizerCache", "Texture Upload", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::UploadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging) {
|
||||
MICROPROFILE_SCOPE(RasterizerCache_TextureUL);
|
||||
|
||||
const FormatTuple& tuple = runtime.GetFormatTuple(pixel_format);
|
||||
|
||||
// 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);
|
||||
|
||||
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;
|
||||
|
||||
unscaled_tex = runtime.Allocate2D(rect.GetWidth(), rect.GetHeight(), pixel_format);
|
||||
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);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, staging.buffer.handle);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
|
||||
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
|
||||
reinterpret_cast<void*>(buffer_offset));
|
||||
|
||||
staging.Lock();
|
||||
|
||||
cur_state.texture_units[0].texture_2d = old_tex;
|
||||
cur_state.Apply();
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
||||
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> from_rect{0, rect.GetHeight(), rect.GetWidth(), 0};
|
||||
/*if (!owner.texture_filterer->Filter(unscaled_tex, from_rect, texture, scaled_rect, type)) {
|
||||
const TextureBlit texture_blit = {
|
||||
.surface_type = type,
|
||||
.src_level = 0,
|
||||
.dst_level = 0,
|
||||
.src_region = Region2D{
|
||||
.start = {0, 0},
|
||||
.end = {width, height}
|
||||
},
|
||||
.dst_region = Region2D{
|
||||
.start = {rect.left, rect.bottom},
|
||||
.end = {rect.right, rect.top}
|
||||
}
|
||||
};
|
||||
|
||||
runtime.BlitTextures(unscaled_tex, texture, texture_blit);
|
||||
}*/
|
||||
}
|
||||
|
||||
InvalidateAllWatcher();
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(RasterizerCache_TextureDL, "RasterizerCache", "Texture Download", MP_RGB(128, 192, 64));
|
||||
void CachedSurface::DownloadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging) {
|
||||
MICROPROFILE_SCOPE(RasterizerCache_TextureDL);
|
||||
|
||||
const FormatTuple& tuple = runtime.GetFormatTuple(pixel_format);
|
||||
|
||||
OpenGLState state = OpenGLState::GetCurState();
|
||||
OpenGLState prev_state = state;
|
||||
SCOPE_EXIT({ prev_state.Apply(); });
|
||||
|
||||
// Ensure no bad interactions with GL_PACK_ALIGNMENT
|
||||
ASSERT(stride * GetBytesPerPixel(pixel_format) % 4 == 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, staging.buffer.handle);
|
||||
const u32 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
|
||||
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;
|
||||
|
||||
OGLTexture unscaled_tex = runtime.Allocate2D(rect.GetWidth(), rect.GetHeight(), pixel_format);
|
||||
|
||||
const VideoCore::TextureBlit texture_blit = {
|
||||
.surface_type = type,
|
||||
.src_level = 0,
|
||||
.dst_level = 0,
|
||||
.src_region = VideoCore::Region2D{
|
||||
.start = {scaled_rect.left, scaled_rect.bottom},
|
||||
.end = {scaled_rect.right, scaled_rect.top}
|
||||
},
|
||||
.dst_region = VideoCore::Region2D{
|
||||
.start = {0, 0},
|
||||
.end = {rect.GetWidth(), rect.GetHeight()}
|
||||
}
|
||||
};
|
||||
|
||||
// Blit scaled texture to the unscaled one
|
||||
runtime.BlitTextures(texture, unscaled_tex, texture_blit);
|
||||
|
||||
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(),
|
||||
reinterpret_cast<void*>(buffer_offset));
|
||||
} else {
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, reinterpret_cast<void*>(buffer_offset));
|
||||
}*/
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, reinterpret_cast<void*>(buffer_offset));
|
||||
} else {
|
||||
const u32 download_size = width * height * GetBytesPerPixel(pixel_format);
|
||||
const VideoCore::BufferTextureCopy texture_download = {
|
||||
.buffer_offset = buffer_offset,
|
||||
.buffer_size = download_size,
|
||||
.buffer_row_length = stride,
|
||||
.buffer_height = height,
|
||||
.surface_type = type,
|
||||
.texture_level = 0,
|
||||
.texture_offset = {rect.bottom, rect.left},
|
||||
.texture_extent = {rect.GetWidth(), rect.GetHeight()}
|
||||
};
|
||||
|
||||
runtime.ReadTexture(texture, texture_download, pixel_format);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
@ -5,12 +5,18 @@
|
||||
#pragma once
|
||||
#include <span>
|
||||
#include <set>
|
||||
#include "video_core/rasterizer_cache/rasterizer_cache.h"
|
||||
#include "video_core/rasterizer_cache/surface_base.h"
|
||||
#include "video_core/rasterizer_cache/types.h"
|
||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct FormatTuple;
|
||||
struct FormatTuple {
|
||||
GLint internal_format;
|
||||
GLenum format;
|
||||
GLenum type;
|
||||
};
|
||||
|
||||
struct StagingBuffer {
|
||||
OGLBuffer buffer{};
|
||||
@ -50,6 +56,7 @@ class Driver;
|
||||
* Separating this into a class makes it easier to abstract graphics API code
|
||||
*/
|
||||
class TextureRuntime {
|
||||
friend class CachedSurface;
|
||||
public:
|
||||
TextureRuntime(Driver& driver);
|
||||
~TextureRuntime() = default;
|
||||
@ -57,18 +64,28 @@ public:
|
||||
/// Maps an internal staging buffer of the provided size of pixel uploads/downloads
|
||||
const StagingBuffer& FindStaging(u32 size, bool upload);
|
||||
|
||||
/// Returns the OpenGL format tuple associated with the provided pixel format
|
||||
const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format);
|
||||
|
||||
/// Allocates a 2D OpenGL texture with the specified dimentions and format
|
||||
OGLTexture Allocate2D(u32 width, u32 height, VideoCore::PixelFormat format);
|
||||
|
||||
/// Allocates an OpenGL cube map texture with the specified dimentions and format
|
||||
OGLTexture AllocateCubeMap(u32 width, VideoCore::PixelFormat format);
|
||||
|
||||
/// Copies the GPU pixel data to the provided pixels buffer
|
||||
void ReadTexture(OGLTexture& texture, const BufferTextureCopy& copy,
|
||||
PixelFormat format, std::span<std::byte> pixels);
|
||||
void ReadTexture(OGLTexture& texture, const VideoCore::BufferTextureCopy& copy,
|
||||
VideoCore::PixelFormat format);
|
||||
|
||||
/// Fills the rectangle of the texture with the clear value provided
|
||||
bool ClearTexture(OGLTexture& texture, const TextureClear& clear, ClearValue value);
|
||||
bool ClearTexture(OGLTexture& texture, const VideoCore::TextureClear& clear,
|
||||
VideoCore::ClearValue value);
|
||||
|
||||
/// Copies a rectangle of src_tex to another rectange of dst_rect
|
||||
bool CopyTextures(OGLTexture& source, OGLTexture& dest, const TextureCopy& copy);
|
||||
bool CopyTextures(OGLTexture& source, OGLTexture& dest, const VideoCore::TextureCopy& copy);
|
||||
|
||||
/// Blits a rectangle of src_tex to another rectange of dst_rect
|
||||
bool BlitTextures(OGLTexture& source, OGLTexture& dest, const TextureBlit& blit);
|
||||
bool BlitTextures(OGLTexture& source, OGLTexture& dest, const VideoCore::TextureBlit& blit);
|
||||
|
||||
/// Generates mipmaps for all the available levels of the texture
|
||||
void GenerateMipmaps(OGLTexture& texture, u32 max_level);
|
||||
@ -76,8 +93,37 @@ public:
|
||||
private:
|
||||
Driver& driver;
|
||||
OGLFramebuffer read_fbo, draw_fbo;
|
||||
std::unordered_multimap<VideoCore::HostTextureTag, OGLTexture> texture2d_recycler;
|
||||
|
||||
// Staging buffers stored in increasing size
|
||||
std::multiset<StagingBuffer> upload_buffers;
|
||||
std::multiset<StagingBuffer> download_buffers;
|
||||
};
|
||||
|
||||
class CachedSurface : public VideoCore::SurfaceBase<CachedSurface> {
|
||||
public:
|
||||
CachedSurface(VideoCore::SurfaceParams& params, TextureRuntime& runtime)
|
||||
: VideoCore::SurfaceBase<CachedSurface>{params}, runtime{runtime} {}
|
||||
~CachedSurface() override = default;
|
||||
|
||||
/// Uploads pixel data in staging to a rectangle region of the surface texture
|
||||
void UploadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging);
|
||||
|
||||
/// Downloads pixel data to staging from a rectangle region of the surface texture
|
||||
void DownloadTexture(Common::Rectangle<u32> rect, const StagingBuffer& staging);
|
||||
|
||||
private:
|
||||
TextureRuntime& runtime;
|
||||
|
||||
public:
|
||||
OGLTexture texture{};
|
||||
};
|
||||
|
||||
struct Traits {
|
||||
using Runtime = TextureRuntime;
|
||||
using Surface = CachedSurface;
|
||||
};
|
||||
|
||||
using RasterizerCache = VideoCore::RasterizerCache<Traits>;
|
||||
|
||||
} // namespace OpenGL
|
@ -8,6 +8,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "video_core/rasterizer_cache/utils.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
#include "video_core/renderer_opengl/gl_texture_runtime.h"
|
||||
#include "video_core/renderer_opengl/texture_downloader_es.h"
|
||||
|
||||
#include "shaders/depth_to_color.frag"
|
||||
@ -16,6 +17,17 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
static constexpr std::array DEPTH_TUPLES_HACK = {
|
||||
FormatTuple{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
|
||||
FormatTuple{},
|
||||
FormatTuple{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
|
||||
FormatTuple{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
|
||||
};
|
||||
|
||||
const FormatTuple& GetFormatTuple(VideoCore::PixelFormat format) {
|
||||
return DEPTH_TUPLES_HACK[static_cast<u32>(format)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Self tests for the texture downloader
|
||||
*/
|
||||
@ -75,13 +87,13 @@ void TextureDownloaderES::Test() {
|
||||
}
|
||||
};
|
||||
LOG_INFO(Render_OpenGL, "GL_DEPTH24_STENCIL8 download test starting");
|
||||
test(GetFormatTuple(PixelFormat::D24S8), std::vector<u32>{}, 4096,
|
||||
test(GetFormatTuple(VideoCore::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(GetFormatTuple(PixelFormat::D24), std::vector<u32>{}, 4096,
|
||||
test(GetFormatTuple(VideoCore::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(GetFormatTuple(PixelFormat::D16), std::vector<u16>{}, 256,
|
||||
test(GetFormatTuple(VideoCore::PixelFormat::D16), std::vector<u16>{}, 256,
|
||||
[](std::size_t idx) { return static_cast<u16>(idx); });
|
||||
|
||||
cur_state.Apply();
|
||||
|
@ -62,10 +62,10 @@ bool TextureFilterer::IsNull() const {
|
||||
|
||||
bool TextureFilterer::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
|
||||
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect,
|
||||
SurfaceType type) {
|
||||
VideoCore::SurfaceType type) {
|
||||
|
||||
// Depth/Stencil texture filtering is not supported for now
|
||||
if (IsNull() || (type != SurfaceType::Color && type != SurfaceType::Texture)) {
|
||||
if (IsNull() || (type != VideoCore::SurfaceType::Color && type != VideoCore::SurfaceType::Texture)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,8 @@ public:
|
||||
|
||||
// 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);
|
||||
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect,
|
||||
VideoCore::SurfaceType type);
|
||||
|
||||
static std::vector<std::string_view> GetFilterNames();
|
||||
|
||||
|
Reference in New Issue
Block a user