yuzu/src/video_core/renderer_opengl/gl_texture_cache.cpp
ReinUsesLisp 82c2601555 video_core: Reimplement the buffer cache
Reimplement the buffer cache using cached bindings and page level
granularity for modification tracking. This also drops the usage of
shared pointers and virtual functions from the cache.

- Bindings are cached, allowing to skip work when the game changes few
  bits between draws.
- OpenGL Assembly shaders no longer copy when a region has been modified
  from the GPU to emulate constant buffers, instead GL_EXT_memory_object
  is used to alias sub-buffers within the same allocation.
- OpenGL Assembly shaders stream constant buffer data using
  glProgramBufferParametersIuivNV, from NV_parameter_buffer_object. In
  theory this should save one hash table resolve inside the driver
  compared to glBufferSubData.
- A new OpenGL stream buffer is implemented based on fences for drivers
  that are not Nvidia's proprietary, due to their low performance on
  partial glBufferSubData calls synchronized with 3D rendering (that
  some games use a lot).
- Most optimizations are shared between APIs now, allowing Vulkan to
  cache more bindings than before, skipping unnecesarry work.

This commit adds the necessary infrastructure to use Vulkan object from
OpenGL. Overall, it improves performance and fixes some bugs present on
the old cache. There are still some edge cases hit by some games that
harm performance on some vendors, this are planned to be fixed in later
commits.
2021-02-13 02:17:22 -03:00

1061 lines
48 KiB
C++

// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <bit>
#include <string>
#include <glad/glad.h>
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"
#include "video_core/renderer_opengl/util_shaders.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/format_lookup_table.h"
#include "video_core/texture_cache/samples_helper.h"
#include "video_core/texture_cache/texture_cache.h"
#include "video_core/textures/decoders.h"
namespace OpenGL {
namespace {
using Tegra::Texture::SwizzleSource;
using Tegra::Texture::TextureMipmapFilter;
using Tegra::Texture::TextureType;
using Tegra::Texture::TICEntry;
using Tegra::Texture::TSCEntry;
using VideoCommon::CalculateLevelStrideAlignment;
using VideoCommon::ImageCopy;
using VideoCommon::ImageFlagBits;
using VideoCommon::ImageType;
using VideoCommon::NUM_RT;
using VideoCommon::SamplesLog2;
using VideoCommon::SwizzleParameters;
using VideoCore::Surface::BytesPerBlock;
using VideoCore::Surface::IsPixelFormatASTC;
using VideoCore::Surface::IsPixelFormatSRGB;
using VideoCore::Surface::MaxPixelFormat;
using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::SurfaceType;
struct CopyOrigin {
GLint level;
GLint x;
GLint y;
GLint z;
};
struct CopyRegion {
GLsizei width;
GLsizei height;
GLsizei depth;
};
struct FormatTuple {
GLenum internal_format;
GLenum format = GL_NONE;
GLenum type = GL_NONE;
};
constexpr std::array<FormatTuple, MaxPixelFormat> FORMAT_TABLE = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_UNORM
{GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, // A8B8G8R8_SNORM
{GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE}, // A8B8G8R8_SINT
{GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}, // A8B8G8R8_UINT
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // R5G6B5_UNORM
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5_REV}, // B5G6R5_UNORM
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1R5G5B5_UNORM
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM
{GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM
{GL_R8_SNORM, GL_RED, GL_BYTE}, // R8_SNORM
{GL_R8I, GL_RED_INTEGER, GL_BYTE}, // R8_SINT
{GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE}, // R8_UINT
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // R16G16B16A16_FLOAT
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_SHORT}, // R16G16B16A16_UNORM
{GL_RGBA16_SNORM, GL_RGBA, GL_SHORT}, // R16G16B16A16_SNORM
{GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT}, // R16G16B16A16_SINT
{GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}, // R16G16B16A16_UINT
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}, // B10G11R11_FLOAT
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT}, // R32G32B32A32_UINT
{GL_COMPRESSED_RGBA_S3TC_DXT1_EXT}, // BC1_RGBA_UNORM
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT}, // BC2_UNORM
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT}, // BC3_UNORM
{GL_COMPRESSED_RED_RGTC1}, // BC4_UNORM
{GL_COMPRESSED_SIGNED_RED_RGTC1}, // BC4_SNORM
{GL_COMPRESSED_RG_RGTC2}, // BC5_UNORM
{GL_COMPRESSED_SIGNED_RG_RGTC2}, // BC5_SNORM
{GL_COMPRESSED_RGBA_BPTC_UNORM}, // BC7_UNORM
{GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT}, // BC6H_UFLOAT
{GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT}, // BC6H_SFLOAT
{GL_COMPRESSED_RGBA_ASTC_4x4_KHR}, // ASTC_2D_4X4_UNORM
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
{GL_RGBA32F, GL_RGBA, GL_FLOAT}, // R32G32B32A32_FLOAT
{GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, // R32G32B32A32_SINT
{GL_RG32F, GL_RG, GL_FLOAT}, // R32G32_FLOAT
{GL_RG32I, GL_RG_INTEGER, GL_INT}, // R32G32_SINT
{GL_R32F, GL_RED, GL_FLOAT}, // R32_FLOAT
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16_FLOAT
{GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16_UNORM
{GL_R16_SNORM, GL_RED, GL_SHORT}, // R16_SNORM
{GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16_UINT
{GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16_SINT
{GL_RG16, GL_RG, GL_UNSIGNED_SHORT}, // R16G16_UNORM
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // R16G16_FLOAT
{GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT}, // R16G16_UINT
{GL_RG16I, GL_RG_INTEGER, GL_SHORT}, // R16G16_SINT
{GL_RG16_SNORM, GL_RG, GL_SHORT}, // R16G16_SNORM
{GL_RGB32F, GL_RGB, GL_FLOAT}, // R32G32B32_FLOAT
{GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV}, // A8B8G8R8_SRGB
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // R8G8_UNORM
{GL_RG8_SNORM, GL_RG, GL_BYTE}, // R8G8_SNORM
{GL_RG8I, GL_RG_INTEGER, GL_BYTE}, // R8G8_SINT
{GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE}, // R8G8_UINT
{GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, // R32G32_UINT
{GL_RGB16F, GL_RGBA, GL_HALF_FLOAT}, // R16G16B16X16_FLOAT
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32_UINT
{GL_R32I, GL_RED_INTEGER, GL_INT}, // R32_SINT
{GL_COMPRESSED_RGBA_ASTC_8x8_KHR}, // ASTC_2D_8X8_UNORM
{GL_COMPRESSED_RGBA_ASTC_8x5_KHR}, // ASTC_2D_8X5_UNORM
{GL_COMPRESSED_RGBA_ASTC_5x4_KHR}, // ASTC_2D_5X4_UNORM
{GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE}, // B8G8R8A8_UNORM
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT}, // BC1_RGBA_SRGB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT}, // BC2_SRGB
{GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT}, // BC3_SRGB
{GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM}, // BC7_SRGB
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4_REV}, // A4B4G4R4_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR}, // ASTC_2D_4X4_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR}, // ASTC_2D_8X8_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR}, // ASTC_2D_8X5_SRGB
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR}, // ASTC_2D_5X4_SRGB
{GL_COMPRESSED_RGBA_ASTC_5x5_KHR}, // ASTC_2D_5X5_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR}, // ASTC_2D_5X5_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x8_KHR}, // ASTC_2D_10X8_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR}, // ASTC_2D_10X8_SRGB
{GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
{GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB
{GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR}, // ASTC_2D_8X6_SRGB
{GL_COMPRESSED_RGBA_ASTC_6x5_KHR}, // ASTC_2D_6X5_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR}, // ASTC_2D_6X5_SRGB
{GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, // E5B9G9R9_FLOAT
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32_FLOAT
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16_UNORM
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24_UNORM_S8_UINT
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // S8_UINT_D24_UNORM
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL,
GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, // D32_FLOAT_S8_UINT
}};
constexpr std::array ACCELERATED_FORMATS{
GL_RGBA32F, GL_RGBA16F, GL_RG32F, GL_RG16F, GL_R11F_G11F_B10F, GL_R32F,
GL_R16F, GL_RGBA32UI, GL_RGBA16UI, GL_RGB10_A2UI, GL_RGBA8UI, GL_RG32UI,
GL_RG16UI, GL_RG8UI, GL_R32UI, GL_R16UI, GL_R8UI, GL_RGBA32I,
GL_RGBA16I, GL_RGBA8I, GL_RG32I, GL_RG16I, GL_RG8I, GL_R32I,
GL_R16I, GL_R8I, GL_RGBA16, GL_RGB10_A2, GL_RGBA8, GL_RG16,
GL_RG8, GL_R16, GL_R8, GL_RGBA16_SNORM, GL_RGBA8_SNORM, GL_RG16_SNORM,
GL_RG8_SNORM, GL_R16_SNORM, GL_R8_SNORM,
};
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
ASSERT(static_cast<size_t>(pixel_format) < FORMAT_TABLE.size());
return FORMAT_TABLE[static_cast<size_t>(pixel_format)];
}
GLenum ImageTarget(const VideoCommon::ImageInfo& info) {
switch (info.type) {
case ImageType::e1D:
return GL_TEXTURE_1D_ARRAY;
case ImageType::e2D:
if (info.num_samples > 1) {
return GL_TEXTURE_2D_MULTISAMPLE_ARRAY;
}
return GL_TEXTURE_2D_ARRAY;
case ImageType::e3D:
return GL_TEXTURE_3D;
case ImageType::Linear:
return GL_TEXTURE_2D_ARRAY;
case ImageType::Buffer:
return GL_TEXTURE_BUFFER;
}
UNREACHABLE_MSG("Invalid image type={}", info.type);
return GL_NONE;
}
GLenum ImageTarget(ImageViewType type, int num_samples = 1) {
const bool is_multisampled = num_samples > 1;
switch (type) {
case ImageViewType::e1D:
return GL_TEXTURE_1D;
case ImageViewType::e2D:
return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
case ImageViewType::Cube:
return GL_TEXTURE_CUBE_MAP;
case ImageViewType::e3D:
return GL_TEXTURE_3D;
case ImageViewType::e1DArray:
return GL_TEXTURE_1D_ARRAY;
case ImageViewType::e2DArray:
return is_multisampled ? GL_TEXTURE_2D_MULTISAMPLE_ARRAY : GL_TEXTURE_2D_ARRAY;
case ImageViewType::CubeArray:
return GL_TEXTURE_CUBE_MAP_ARRAY;
case ImageViewType::Rect:
return GL_TEXTURE_RECTANGLE;
case ImageViewType::Buffer:
return GL_TEXTURE_BUFFER;
}
UNREACHABLE_MSG("Invalid image view type={}", type);
return GL_NONE;
}
GLenum TextureMode(PixelFormat format, bool is_first) {
switch (format) {
case PixelFormat::D24_UNORM_S8_UINT:
case PixelFormat::D32_FLOAT_S8_UINT:
return is_first ? GL_DEPTH_COMPONENT : GL_STENCIL_INDEX;
case PixelFormat::S8_UINT_D24_UNORM:
return is_first ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT;
default:
UNREACHABLE();
return GL_DEPTH_COMPONENT;
}
}
GLint Swizzle(SwizzleSource source) {
switch (source) {
case SwizzleSource::Zero:
return GL_ZERO;
case SwizzleSource::R:
return GL_RED;
case SwizzleSource::G:
return GL_GREEN;
case SwizzleSource::B:
return GL_BLUE;
case SwizzleSource::A:
return GL_ALPHA;
case SwizzleSource::OneInt:
case SwizzleSource::OneFloat:
return GL_ONE;
}
UNREACHABLE_MSG("Invalid swizzle source={}", source);
return GL_NONE;
}
GLenum AttachmentType(PixelFormat format) {
switch (const SurfaceType type = VideoCore::Surface::GetFormatType(format); type) {
case SurfaceType::Depth:
return GL_DEPTH_ATTACHMENT;
case SurfaceType::DepthStencil:
return GL_DEPTH_STENCIL_ATTACHMENT;
default:
UNIMPLEMENTED_MSG("Unimplemented type={}", type);
return GL_NONE;
}
}
[[nodiscard]] bool IsConverted(const Device& device, PixelFormat format, ImageType type) {
if (!device.HasASTC() && IsPixelFormatASTC(format)) {
return true;
}
switch (format) {
case PixelFormat::BC4_UNORM:
case PixelFormat::BC5_UNORM:
return type == ImageType::e3D;
default:
break;
}
return false;
}
[[nodiscard]] constexpr SwizzleSource ConvertGreenRed(SwizzleSource value) {
switch (value) {
case SwizzleSource::G:
return SwizzleSource::R;
default:
return value;
}
}
void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4> swizzle) {
switch (format) {
case PixelFormat::D24_UNORM_S8_UINT:
case PixelFormat::D32_FLOAT_S8_UINT:
case PixelFormat::S8_UINT_D24_UNORM:
UNIMPLEMENTED_IF(swizzle[0] != SwizzleSource::R && swizzle[0] != SwizzleSource::G);
glTextureParameteri(handle, GL_DEPTH_STENCIL_TEXTURE_MODE,
TextureMode(format, swizzle[0] == SwizzleSource::R));
std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
break;
default:
break;
}
std::array<GLint, 4> gl_swizzle;
std::ranges::transform(swizzle, gl_swizzle.begin(), Swizzle);
glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, gl_swizzle.data());
}
[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,
const VideoCommon::ImageInfo& info) {
// Disable accelerated uploads for now as they don't implement swizzled uploads
return false;
switch (info.type) {
case ImageType::e2D:
case ImageType::e3D:
case ImageType::Linear:
break;
default:
return false;
}
const GLenum internal_format = GetFormatTuple(info.format).internal_format;
const auto& format_info = runtime.FormatInfo(info.type, internal_format);
if (format_info.is_compressed) {
return false;
}
if (std::ranges::find(ACCELERATED_FORMATS, internal_format) == ACCELERATED_FORMATS.end()) {
return false;
}
if (format_info.compatibility_by_size) {
return true;
}
const GLenum store_format = StoreFormat(BytesPerBlock(info.format));
const GLenum store_class = runtime.FormatInfo(info.type, store_format).compatibility_class;
return format_info.compatibility_class == store_class;
}
[[nodiscard]] CopyOrigin MakeCopyOrigin(VideoCommon::Offset3D offset,
VideoCommon::SubresourceLayers subresource, GLenum target) {
switch (target) {
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
return CopyOrigin{
.level = static_cast<GLint>(subresource.base_level),
.x = static_cast<GLint>(offset.x),
.y = static_cast<GLint>(offset.y),
.z = static_cast<GLint>(subresource.base_layer),
};
case GL_TEXTURE_3D:
return CopyOrigin{
.level = static_cast<GLint>(subresource.base_level),
.x = static_cast<GLint>(offset.x),
.y = static_cast<GLint>(offset.y),
.z = static_cast<GLint>(offset.z),
};
default:
UNIMPLEMENTED_MSG("Unimplemented copy target={}", target);
return CopyOrigin{.level = 0, .x = 0, .y = 0, .z = 0};
}
}
[[nodiscard]] CopyRegion MakeCopyRegion(VideoCommon::Extent3D extent,
VideoCommon::SubresourceLayers dst_subresource,
GLenum target) {
switch (target) {
case GL_TEXTURE_2D_ARRAY:
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY:
return CopyRegion{
.width = static_cast<GLsizei>(extent.width),
.height = static_cast<GLsizei>(extent.height),
.depth = static_cast<GLsizei>(dst_subresource.num_layers),
};
case GL_TEXTURE_3D:
return CopyRegion{
.width = static_cast<GLsizei>(extent.width),
.height = static_cast<GLsizei>(extent.height),
.depth = static_cast<GLsizei>(extent.depth),
};
default:
UNIMPLEMENTED_MSG("Unimplemented copy target={}", target);
return CopyRegion{.width = 0, .height = 0, .depth = 0};
}
}
void AttachTexture(GLuint fbo, GLenum attachment, const ImageView* image_view) {
if (False(image_view->flags & VideoCommon::ImageViewFlagBits::Slice)) {
const GLuint texture = image_view->DefaultHandle();
glNamedFramebufferTexture(fbo, attachment, texture, 0);
return;
}
const GLuint texture = image_view->Handle(ImageViewType::e3D);
if (image_view->range.extent.layers > 1) {
// TODO: OpenGL doesn't support rendering to a fixed number of slices
glNamedFramebufferTexture(fbo, attachment, texture, 0);
} else {
const u32 slice = image_view->range.base.layer;
glNamedFramebufferTextureLayer(fbo, attachment, texture, 0, slice);
}
}
} // Anonymous namespace
ImageBufferMap::~ImageBufferMap() {
if (sync) {
sync->Create();
}
}
TextureCacheRuntime::TextureCacheRuntime(const Device& device_, ProgramManager& program_manager,
StateTracker& state_tracker_)
: device{device_}, state_tracker{state_tracker_}, util_shaders(program_manager) {
static constexpr std::array TARGETS{GL_TEXTURE_1D_ARRAY, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_3D};
for (size_t i = 0; i < TARGETS.size(); ++i) {
const GLenum target = TARGETS[i];
for (const FormatTuple& tuple : FORMAT_TABLE) {
const GLenum format = tuple.internal_format;
GLint compat_class;
GLint compat_type;
GLint is_compressed;
glGetInternalformativ(target, format, GL_IMAGE_COMPATIBILITY_CLASS, 1, &compat_class);
glGetInternalformativ(target, format, GL_IMAGE_FORMAT_COMPATIBILITY_TYPE, 1,
&compat_type);
glGetInternalformativ(target, format, GL_TEXTURE_COMPRESSED, 1, &is_compressed);
const FormatProperties properties{
.compatibility_class = static_cast<GLenum>(compat_class),
.compatibility_by_size = compat_type == GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE,
.is_compressed = is_compressed == GL_TRUE,
};
format_properties[i].emplace(format, properties);
}
}
has_broken_texture_view_formats = device.HasBrokenTextureViewFormats();
null_image_1d_array.Create(GL_TEXTURE_1D_ARRAY);
null_image_cube_array.Create(GL_TEXTURE_CUBE_MAP_ARRAY);
null_image_3d.Create(GL_TEXTURE_3D);
null_image_rect.Create(GL_TEXTURE_RECTANGLE);
glTextureStorage2D(null_image_1d_array.handle, 1, GL_R8, 1, 1);
glTextureStorage3D(null_image_cube_array.handle, 1, GL_R8, 1, 1, 6);
glTextureStorage3D(null_image_3d.handle, 1, GL_R8, 1, 1, 1);
glTextureStorage2D(null_image_rect.handle, 1, GL_R8, 1, 1);
std::array<GLuint, 4> new_handles;
glGenTextures(static_cast<GLsizei>(new_handles.size()), new_handles.data());
null_image_view_1d.handle = new_handles[0];
null_image_view_2d.handle = new_handles[1];
null_image_view_2d_array.handle = new_handles[2];
null_image_view_cube.handle = new_handles[3];
glTextureView(null_image_view_1d.handle, GL_TEXTURE_1D, null_image_1d_array.handle, GL_R8, 0, 1,
0, 1);
glTextureView(null_image_view_2d.handle, GL_TEXTURE_2D, null_image_cube_array.handle, GL_R8, 0,
1, 0, 1);
glTextureView(null_image_view_2d_array.handle, GL_TEXTURE_2D_ARRAY,
null_image_cube_array.handle, GL_R8, 0, 1, 0, 1);
glTextureView(null_image_view_cube.handle, GL_TEXTURE_CUBE_MAP, null_image_cube_array.handle,
GL_R8, 0, 1, 0, 6);
const std::array texture_handles{
null_image_1d_array.handle, null_image_cube_array.handle, null_image_3d.handle,
null_image_rect.handle, null_image_view_1d.handle, null_image_view_2d.handle,
null_image_view_2d_array.handle, null_image_view_cube.handle,
};
for (const GLuint handle : texture_handles) {
static constexpr std::array NULL_SWIZZLE{GL_ZERO, GL_ZERO, GL_ZERO, GL_ZERO};
glTextureParameteriv(handle, GL_TEXTURE_SWIZZLE_RGBA, NULL_SWIZZLE.data());
}
const auto set_view = [this](ImageViewType type, GLuint handle) {
if (device.HasDebuggingToolAttached()) {
const std::string name = fmt::format("NullImage {}", type);
glObjectLabel(GL_TEXTURE, handle, static_cast<GLsizei>(name.size()), name.data());
}
null_image_views[static_cast<size_t>(type)] = handle;
};
set_view(ImageViewType::e1D, null_image_view_1d.handle);
set_view(ImageViewType::e2D, null_image_view_2d.handle);
set_view(ImageViewType::Cube, null_image_view_cube.handle);
set_view(ImageViewType::e3D, null_image_3d.handle);
set_view(ImageViewType::e1DArray, null_image_1d_array.handle);
set_view(ImageViewType::e2DArray, null_image_view_2d_array.handle);
set_view(ImageViewType::CubeArray, null_image_cube_array.handle);
set_view(ImageViewType::Rect, null_image_rect.handle);
}
TextureCacheRuntime::~TextureCacheRuntime() = default;
void TextureCacheRuntime::Finish() {
glFinish();
}
ImageBufferMap TextureCacheRuntime::UploadStagingBuffer(size_t size) {
return upload_buffers.RequestMap(size, true);
}
ImageBufferMap TextureCacheRuntime::DownloadStagingBuffer(size_t size) {
return download_buffers.RequestMap(size, false);
}
void TextureCacheRuntime::CopyImage(Image& dst_image, Image& src_image,
std::span<const ImageCopy> copies) {
const GLuint dst_name = dst_image.Handle();
const GLuint src_name = src_image.Handle();
const GLenum dst_target = ImageTarget(dst_image.info);
const GLenum src_target = ImageTarget(src_image.info);
for (const ImageCopy& copy : copies) {
const auto src_origin = MakeCopyOrigin(copy.src_offset, copy.src_subresource, src_target);
const auto dst_origin = MakeCopyOrigin(copy.dst_offset, copy.dst_subresource, dst_target);
const auto region = MakeCopyRegion(copy.extent, copy.dst_subresource, dst_target);
glCopyImageSubData(src_name, src_target, src_origin.level, src_origin.x, src_origin.y,
src_origin.z, dst_name, dst_target, dst_origin.level, dst_origin.x,
dst_origin.y, dst_origin.z, region.width, region.height, region.depth);
}
}
bool TextureCacheRuntime::CanImageBeCopied(const Image& dst, const Image& src) {
if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) {
return false;
}
return true;
}
void TextureCacheRuntime::EmulateCopyImage(Image& dst, Image& src,
std::span<const ImageCopy> copies) {
if (dst.info.type == ImageType::e3D && dst.info.format == PixelFormat::BC4_UNORM) {
ASSERT(src.info.type == ImageType::e3D);
util_shaders.CopyBC4(dst, src, copies);
} else {
UNREACHABLE();
}
}
void TextureCacheRuntime::BlitFramebuffer(Framebuffer* dst, Framebuffer* src,
const std::array<Offset2D, 2>& dst_region,
const std::array<Offset2D, 2>& src_region,
Tegra::Engines::Fermi2D::Filter filter,
Tegra::Engines::Fermi2D::Operation operation) {
state_tracker.NotifyScissor0();
state_tracker.NotifyRasterizeEnable();
state_tracker.NotifyFramebufferSRGB();
ASSERT(dst->BufferBits() == src->BufferBits());
glEnable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_RASTERIZER_DISCARD);
glDisablei(GL_SCISSOR_TEST, 0);
const GLbitfield buffer_bits = dst->BufferBits();
const bool has_depth = (buffer_bits & ~GL_COLOR_BUFFER_BIT) != 0;
const bool is_linear = !has_depth && filter == Tegra::Engines::Fermi2D::Filter::Bilinear;
glBlitNamedFramebuffer(src->Handle(), dst->Handle(), src_region[0].x, src_region[0].y,
src_region[1].x, src_region[1].y, dst_region[0].x, dst_region[0].y,
dst_region[1].x, dst_region[1].y, buffer_bits,
is_linear ? GL_LINEAR : GL_NEAREST);
}
void TextureCacheRuntime::AccelerateImageUpload(Image& image, const ImageBufferMap& map,
size_t buffer_offset,
std::span<const SwizzleParameters> swizzles) {
switch (image.info.type) {
case ImageType::e2D:
return util_shaders.BlockLinearUpload2D(image, map, buffer_offset, swizzles);
case ImageType::e3D:
return util_shaders.BlockLinearUpload3D(image, map, buffer_offset, swizzles);
case ImageType::Linear:
return util_shaders.PitchUpload(image, map, buffer_offset, swizzles);
default:
UNREACHABLE();
break;
}
}
void TextureCacheRuntime::InsertUploadMemoryBarrier() {
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
}
FormatProperties TextureCacheRuntime::FormatInfo(ImageType type, GLenum internal_format) const {
switch (type) {
case ImageType::e1D:
return format_properties[0].at(internal_format);
case ImageType::e2D:
case ImageType::Linear:
return format_properties[1].at(internal_format);
case ImageType::e3D:
return format_properties[2].at(internal_format);
default:
UNREACHABLE();
return FormatProperties{};
}
}
TextureCacheRuntime::StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_)
: storage_flags{storage_flags_}, map_flags{map_flags_} {}
TextureCacheRuntime::StagingBuffers::~StagingBuffers() = default;
ImageBufferMap TextureCacheRuntime::StagingBuffers::RequestMap(size_t requested_size,
bool insert_fence) {
const size_t index = RequestBuffer(requested_size);
OGLSync* const sync = insert_fence ? &syncs[index] : nullptr;
return ImageBufferMap{
.mapped_span = std::span(maps[index], requested_size),
.sync = sync,
.buffer = buffers[index].handle,
};
}
size_t TextureCacheRuntime::StagingBuffers::RequestBuffer(size_t requested_size) {
if (const std::optional<size_t> index = FindBuffer(requested_size); index) {
return *index;
}
OGLBuffer& buffer = buffers.emplace_back();
buffer.Create();
glNamedBufferStorage(buffer.handle, requested_size, nullptr,
storage_flags | GL_MAP_PERSISTENT_BIT);
maps.push_back(static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, requested_size,
map_flags | GL_MAP_PERSISTENT_BIT)));
syncs.emplace_back();
sizes.push_back(requested_size);
ASSERT(syncs.size() == buffers.size() && buffers.size() == maps.size() &&
maps.size() == sizes.size());
return buffers.size() - 1;
}
std::optional<size_t> TextureCacheRuntime::StagingBuffers::FindBuffer(size_t requested_size) {
size_t smallest_buffer = std::numeric_limits<size_t>::max();
std::optional<size_t> found;
const size_t num_buffers = sizes.size();
for (size_t index = 0; index < num_buffers; ++index) {
const size_t buffer_size = sizes[index];
if (buffer_size < requested_size || buffer_size >= smallest_buffer) {
continue;
}
if (syncs[index].handle != 0) {
GLint status;
glGetSynciv(syncs[index].handle, GL_SYNC_STATUS, 1, nullptr, &status);
if (status != GL_SIGNALED) {
continue;
}
syncs[index].Release();
}
smallest_buffer = buffer_size;
found = index;
}
return found;
}
Image::Image(TextureCacheRuntime& runtime, const VideoCommon::ImageInfo& info_, GPUVAddr gpu_addr_,
VAddr cpu_addr_)
: VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_) {
if (CanBeAccelerated(runtime, info)) {
flags |= ImageFlagBits::AcceleratedUpload;
}
if (IsConverted(runtime.device, info.format, info.type)) {
flags |= ImageFlagBits::Converted;
gl_internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
gl_format = GL_RGBA;
gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
} else {
const auto& tuple = GetFormatTuple(info.format);
gl_internal_format = tuple.internal_format;
gl_format = tuple.format;
gl_type = tuple.type;
}
const GLenum target = ImageTarget(info);
const GLsizei width = info.size.width;
const GLsizei height = info.size.height;
const GLsizei depth = info.size.depth;
const int max_host_mip_levels = std::bit_width(info.size.width);
const GLsizei num_levels = std::min(info.resources.levels, max_host_mip_levels);
const GLsizei num_layers = info.resources.layers;
const GLsizei num_samples = info.num_samples;
GLuint handle = 0;
if (target != GL_TEXTURE_BUFFER) {
texture.Create(target);
handle = texture.handle;
}
switch (target) {
case GL_TEXTURE_1D_ARRAY:
glTextureStorage2D(handle, num_levels, gl_internal_format, width, num_layers);
break;
case GL_TEXTURE_2D_ARRAY:
glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, num_layers);
break;
case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: {
// TODO: Where should 'fixedsamplelocations' come from?
const auto [samples_x, samples_y] = SamplesLog2(info.num_samples);
glTextureStorage3DMultisample(handle, num_samples, gl_internal_format, width >> samples_x,
height >> samples_y, num_layers, GL_FALSE);
break;
}
case GL_TEXTURE_RECTANGLE:
glTextureStorage2D(handle, num_levels, gl_internal_format, width, height);
break;
case GL_TEXTURE_3D:
glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, depth);
break;
case GL_TEXTURE_BUFFER:
buffer.Create();
glNamedBufferStorage(buffer.handle, guest_size_bytes, nullptr, 0);
break;
default:
UNREACHABLE_MSG("Invalid target=0x{:x}", target);
break;
}
if (runtime.device.HasDebuggingToolAttached()) {
const std::string name = VideoCommon::Name(*this);
glObjectLabel(target == GL_TEXTURE_BUFFER ? GL_BUFFER : GL_TEXTURE, handle,
static_cast<GLsizei>(name.size()), name.data());
}
}
void Image::UploadMemory(const ImageBufferMap& map, size_t buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, map.buffer);
glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, buffer_offset, unswizzled_size_bytes);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
u32 current_row_length = std::numeric_limits<u32>::max();
u32 current_image_height = std::numeric_limits<u32>::max();
for (const VideoCommon::BufferImageCopy& copy : copies) {
if (current_row_length != copy.buffer_row_length) {
current_row_length = copy.buffer_row_length;
glPixelStorei(GL_UNPACK_ROW_LENGTH, current_row_length);
}
if (current_image_height != copy.buffer_image_height) {
current_image_height = copy.buffer_image_height;
glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, current_image_height);
}
CopyBufferToImage(copy, buffer_offset);
}
}
void Image::UploadMemory(const ImageBufferMap& map, size_t buffer_offset,
std::span<const VideoCommon::BufferCopy> copies) {
for (const VideoCommon::BufferCopy& copy : copies) {
glCopyNamedBufferSubData(map.buffer, buffer.handle, copy.src_offset + buffer_offset,
copy.dst_offset, copy.size);
}
}
void Image::DownloadMemory(ImageBufferMap& map, size_t buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); // TODO: Move this to its own API
glBindBuffer(GL_PIXEL_PACK_BUFFER, map.buffer);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
u32 current_row_length = std::numeric_limits<u32>::max();
u32 current_image_height = std::numeric_limits<u32>::max();
for (const VideoCommon::BufferImageCopy& copy : copies) {
if (current_row_length != copy.buffer_row_length) {
current_row_length = copy.buffer_row_length;
glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
}
if (current_image_height != copy.buffer_image_height) {
current_image_height = copy.buffer_image_height;
glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height);
}
CopyImageToBuffer(copy, buffer_offset);
}
}
void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) {
// Compressed formats don't have a pixel format or type
const bool is_compressed = gl_format == GL_NONE;
const void* const offset = reinterpret_cast<const void*>(copy.buffer_offset + buffer_offset);
switch (info.type) {
case ImageType::e1D:
if (is_compressed) {
glCompressedTextureSubImage2D(texture.handle, copy.image_subresource.base_level,
copy.image_offset.x, copy.image_subresource.base_layer,
copy.image_extent.width,
copy.image_subresource.num_layers, gl_internal_format,
static_cast<GLsizei>(copy.buffer_size), offset);
} else {
glTextureSubImage2D(texture.handle, copy.image_subresource.base_level,
copy.image_offset.x, copy.image_subresource.base_layer,
copy.image_extent.width, copy.image_subresource.num_layers,
gl_format, gl_type, offset);
}
break;
case ImageType::e2D:
case ImageType::Linear:
if (is_compressed) {
glCompressedTextureSubImage3D(
texture.handle, copy.image_subresource.base_level, copy.image_offset.x,
copy.image_offset.y, copy.image_subresource.base_layer, copy.image_extent.width,
copy.image_extent.height, copy.image_subresource.num_layers, gl_internal_format,
static_cast<GLsizei>(copy.buffer_size), offset);
} else {
glTextureSubImage3D(texture.handle, copy.image_subresource.base_level,
copy.image_offset.x, copy.image_offset.y,
copy.image_subresource.base_layer, copy.image_extent.width,
copy.image_extent.height, copy.image_subresource.num_layers,
gl_format, gl_type, offset);
}
break;
case ImageType::e3D:
if (is_compressed) {
glCompressedTextureSubImage3D(
texture.handle, copy.image_subresource.base_level, copy.image_offset.x,
copy.image_offset.y, copy.image_offset.z, copy.image_extent.width,
copy.image_extent.height, copy.image_extent.depth, gl_internal_format,
static_cast<GLsizei>(copy.buffer_size), offset);
} else {
glTextureSubImage3D(texture.handle, copy.image_subresource.base_level,
copy.image_offset.x, copy.image_offset.y, copy.image_offset.z,
copy.image_extent.width, copy.image_extent.height,
copy.image_extent.depth, gl_format, gl_type, offset);
}
break;
default:
UNREACHABLE();
}
}
void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t buffer_offset) {
const GLint x_offset = copy.image_offset.x;
const GLsizei width = copy.image_extent.width;
const GLint level = copy.image_subresource.base_level;
const GLsizei buffer_size = static_cast<GLsizei>(copy.buffer_size);
void* const offset = reinterpret_cast<void*>(copy.buffer_offset + buffer_offset);
GLint y_offset = 0;
GLint z_offset = 0;
GLsizei height = 1;
GLsizei depth = 1;
switch (info.type) {
case ImageType::e1D:
y_offset = copy.image_subresource.base_layer;
height = copy.image_subresource.num_layers;
break;
case ImageType::e2D:
case ImageType::Linear:
y_offset = copy.image_offset.y;
z_offset = copy.image_subresource.base_layer;
height = copy.image_extent.height;
depth = copy.image_subresource.num_layers;
break;
case ImageType::e3D:
y_offset = copy.image_offset.y;
z_offset = copy.image_offset.z;
height = copy.image_extent.height;
depth = copy.image_extent.depth;
break;
default:
UNREACHABLE();
}
// Compressed formats don't have a pixel format or type
const bool is_compressed = gl_format == GL_NONE;
if (is_compressed) {
glGetCompressedTextureSubImage(texture.handle, level, x_offset, y_offset, z_offset, width,
height, depth, buffer_size, offset);
} else {
glGetTextureSubImage(texture.handle, level, x_offset, y_offset, z_offset, width, height,
depth, gl_format, gl_type, buffer_size, offset);
}
}
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info,
ImageId image_id_, Image& image)
: VideoCommon::ImageViewBase{info, image.info, image_id_}, views{runtime.null_image_views} {
const Device& device = runtime.device;
if (True(image.flags & ImageFlagBits::Converted)) {
internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
} else {
internal_format = GetFormatTuple(format).internal_format;
}
VideoCommon::SubresourceRange flatten_range = info.range;
std::array<GLuint, 2> handles;
stored_views.reserve(2);
switch (info.type) {
case ImageViewType::e1DArray:
flatten_range.extent.layers = 1;
[[fallthrough]];
case ImageViewType::e1D:
glGenTextures(2, handles.data());
SetupView(device, image, ImageViewType::e1D, handles[0], info, flatten_range);
SetupView(device, image, ImageViewType::e1DArray, handles[1], info, info.range);
break;
case ImageViewType::e2DArray:
flatten_range.extent.layers = 1;
[[fallthrough]];
case ImageViewType::e2D:
if (True(flags & VideoCommon::ImageViewFlagBits::Slice)) {
// 2D and 2D array views on a 3D textures are used exclusively for render targets
ASSERT(info.range.extent.levels == 1);
const VideoCommon::SubresourceRange slice_range{
.base = {.level = info.range.base.level, .layer = 0},
.extent = {.levels = 1, .layers = 1},
};
glGenTextures(1, handles.data());
SetupView(device, image, ImageViewType::e3D, handles[0], info, slice_range);
break;
}
glGenTextures(2, handles.data());
SetupView(device, image, ImageViewType::e2D, handles[0], info, flatten_range);
SetupView(device, image, ImageViewType::e2DArray, handles[1], info, info.range);
break;
case ImageViewType::e3D:
glGenTextures(1, handles.data());
SetupView(device, image, ImageViewType::e3D, handles[0], info, info.range);
break;
case ImageViewType::CubeArray:
flatten_range.extent.layers = 6;
[[fallthrough]];
case ImageViewType::Cube:
glGenTextures(2, handles.data());
SetupView(device, image, ImageViewType::Cube, handles[0], info, flatten_range);
SetupView(device, image, ImageViewType::CubeArray, handles[1], info, info.range);
break;
case ImageViewType::Rect:
glGenTextures(1, handles.data());
SetupView(device, image, ImageViewType::Rect, handles[0], info, info.range);
break;
case ImageViewType::Buffer:
glCreateTextures(GL_TEXTURE_BUFFER, 1, handles.data());
SetupView(device, image, ImageViewType::Buffer, handles[0], info, info.range);
break;
}
default_handle = Handle(info.type);
}
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageParams& params)
: VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {}
void ImageView::SetupView(const Device& device, Image& image, ImageViewType view_type,
GLuint handle, const VideoCommon::ImageViewInfo& info,
VideoCommon::SubresourceRange view_range) {
if (info.type == ImageViewType::Buffer) {
// TODO: Take offset from buffer cache
glTextureBufferRange(handle, internal_format, image.buffer.handle, 0,
image.guest_size_bytes);
} else {
const GLuint parent = image.texture.handle;
const GLenum target = ImageTarget(view_type, image.info.num_samples);
glTextureView(handle, target, parent, internal_format, view_range.base.level,
view_range.extent.levels, view_range.base.layer, view_range.extent.layers);
if (!info.IsRenderTarget()) {
ApplySwizzle(handle, format, info.Swizzle());
}
}
if (device.HasDebuggingToolAttached()) {
const std::string name = VideoCommon::Name(*this, view_type);
glObjectLabel(GL_TEXTURE, handle, static_cast<GLsizei>(name.size()), name.data());
}
stored_views.emplace_back().handle = handle;
views[static_cast<size_t>(view_type)] = handle;
}
Sampler::Sampler(TextureCacheRuntime& runtime, const TSCEntry& config) {
const GLenum compare_mode = config.depth_compare_enabled ? GL_COMPARE_REF_TO_TEXTURE : GL_NONE;
const GLenum compare_func = MaxwellToGL::DepthCompareFunc(config.depth_compare_func);
const GLenum mag = MaxwellToGL::TextureFilterMode(config.mag_filter, TextureMipmapFilter::None);
const GLenum min = MaxwellToGL::TextureFilterMode(config.min_filter, config.mipmap_filter);
const GLenum reduction_filter = MaxwellToGL::ReductionFilter(config.reduction_filter);
const GLint seamless = config.cubemap_interface_filtering ? GL_TRUE : GL_FALSE;
UNIMPLEMENTED_IF(config.cubemap_anisotropy != 1);
UNIMPLEMENTED_IF(config.float_coord_normalization != 0);
sampler.Create();
const GLuint handle = sampler.handle;
glSamplerParameteri(handle, GL_TEXTURE_WRAP_S, MaxwellToGL::WrapMode(config.wrap_u));
glSamplerParameteri(handle, GL_TEXTURE_WRAP_T, MaxwellToGL::WrapMode(config.wrap_v));
glSamplerParameteri(handle, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(config.wrap_p));
glSamplerParameteri(handle, GL_TEXTURE_COMPARE_MODE, compare_mode);
glSamplerParameteri(handle, GL_TEXTURE_COMPARE_FUNC, compare_func);
glSamplerParameteri(handle, GL_TEXTURE_MAG_FILTER, mag);
glSamplerParameteri(handle, GL_TEXTURE_MIN_FILTER, min);
glSamplerParameterf(handle, GL_TEXTURE_LOD_BIAS, config.LodBias());
glSamplerParameterf(handle, GL_TEXTURE_MIN_LOD, config.MinLod());
glSamplerParameterf(handle, GL_TEXTURE_MAX_LOD, config.MaxLod());
glSamplerParameterfv(handle, GL_TEXTURE_BORDER_COLOR, config.BorderColor().data());
if (GLAD_GL_ARB_texture_filter_anisotropic || GLAD_GL_EXT_texture_filter_anisotropic) {
glSamplerParameterf(handle, GL_TEXTURE_MAX_ANISOTROPY, config.MaxAnisotropy());
} else {
LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_anisotropic is required");
}
if (GLAD_GL_ARB_texture_filter_minmax || GLAD_GL_EXT_texture_filter_minmax) {
glSamplerParameteri(handle, GL_TEXTURE_REDUCTION_MODE_ARB, reduction_filter);
} else if (reduction_filter != GL_WEIGHTED_AVERAGE_ARB) {
LOG_WARNING(Render_OpenGL, "GL_ARB_texture_filter_minmax is required");
}
if (GLAD_GL_ARB_seamless_cubemap_per_texture || GLAD_GL_AMD_seamless_cubemap_per_texture) {
glSamplerParameteri(handle, GL_TEXTURE_CUBE_MAP_SEAMLESS, seamless);
} else if (seamless == GL_FALSE) {
// We default to false because it's more common
LOG_WARNING(Render_OpenGL, "GL_ARB_seamless_cubemap_per_texture is required");
}
}
Framebuffer::Framebuffer(TextureCacheRuntime& runtime, std::span<ImageView*, NUM_RT> color_buffers,
ImageView* depth_buffer, const VideoCommon::RenderTargets& key) {
// Bind to READ_FRAMEBUFFER to stop Nvidia's driver from creating an EXT_framebuffer instead of
// a core framebuffer. EXT framebuffer attachments have to match in size and can be shared
// across contexts. yuzu doesn't share framebuffers across contexts and we need attachments with
// mismatching size, this is why core framebuffers are preferred.
GLuint handle;
glGenFramebuffers(1, &handle);
glBindFramebuffer(GL_READ_FRAMEBUFFER, handle);
GLsizei num_buffers = 0;
std::array<GLenum, NUM_RT> gl_draw_buffers;
gl_draw_buffers.fill(GL_NONE);
for (size_t index = 0; index < color_buffers.size(); ++index) {
const ImageView* const image_view = color_buffers[index];
if (!image_view) {
continue;
}
buffer_bits |= GL_COLOR_BUFFER_BIT;
gl_draw_buffers[index] = GL_COLOR_ATTACHMENT0 + key.draw_buffers[index];
num_buffers = static_cast<GLsizei>(index + 1);
const GLenum attachment = static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index);
AttachTexture(handle, attachment, image_view);
}
if (const ImageView* const image_view = depth_buffer; image_view) {
if (GetFormatType(image_view->format) == SurfaceType::DepthStencil) {
buffer_bits |= GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT;
} else {
buffer_bits |= GL_DEPTH_BUFFER_BIT;
}
const GLenum attachment = AttachmentType(image_view->format);
AttachTexture(handle, attachment, image_view);
}
if (num_buffers > 1) {
glNamedFramebufferDrawBuffers(handle, num_buffers, gl_draw_buffers.data());
} else if (num_buffers > 0) {
glNamedFramebufferDrawBuffer(handle, gl_draw_buffers[0]);
} else {
glNamedFramebufferDrawBuffer(handle, GL_NONE);
}
glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_WIDTH, key.size.width);
glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_HEIGHT, key.size.height);
// TODO
// glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_LAYERS, ...);
// glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_SAMPLES, ...);
// glNamedFramebufferParameteri(handle, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS, ...);
if (runtime.device.HasDebuggingToolAttached()) {
const std::string name = VideoCommon::Name(key);
glObjectLabel(GL_FRAMEBUFFER, handle, static_cast<GLsizei>(name.size()), name.data());
}
framebuffer.handle = handle;
}
} // namespace OpenGL