Rasterizer cache refactor (#6375)

* rasterizer_cache: Remove custom texture code

* It's a hacky buggy mess, will be reimplemented later when the cache is in a better state

* rasterizer_cache: Refactor surface upload/download

* Switch to the texture_codec header which was written as part of the vulkan backend by steveice and me

* Move most of the upload logic to the rasterizer cache and out of the surface object

* Scaled uploads/downloads have been disabled for now since they require more runtime infrastructure

* rasterizer_cache: Refactor runtime interface

* Remove aspect enum which is the same as SurfaceType

* Replace Subresource with specific structures for each operation (blit/copy/clear). This mimics moderns APIs vulkan much better

* Pass the surface to the runtime instead of the texture

* Implement CopyTextures with glCopyImageSubData which is available on 4.3 and gles.
  This function also has an overload for cubes which will be removed later.

* rasterizer_cache: Move texture allocation to the runtime

* renderer_opengl: Remove TextureDownloaderES

* It's overly compilcated and unused at the moment. Will be replaced with a simple compute shader in a later commit

* rasterizer_cache: Split CachedSurface

* This commit splits CachedSurface into two classes, SurfaceBase which contains the backend agnostic functions and Surface which is the opengl specific part

* For now the cache uses the opengl surface directly and there are a few ugly casts with watchers, those will be taken care of when the template convertion and watcher removal are added respectively

* rasterizer_cache: Move reinterpreters to the runtime

* rasterizer_cache: Move some pixel format function to the cpp file

* rasterizer_cache: Common texture acceleration functions

* They don't contain any backend specific code so they shouldn't be duplicated

* rasterizer_cache: Remove BlitSurfaces

* It's better to prefer copy/blit in the caller anyway

* rasterizer_cache: Only allocate needed levels

* rasterizer_cache: Move texture runtime out of common dir

* Also shorten the util header filename

* surface_params: Cleanup code

* Add more comments, organize it a bit etc

* rasterizer_cache: Move texture filtering to the runtime

* rasterizer_cache: Move to VideoCore

* renderer_opengl: Reimplement scaled uploads/downloads

* Instead of looking up for temporary textures, each allocation now contains both a scaled and unscaled handle
  This allows the scale operations to be done inside the surface object itself and improves performance in general

* In particular the scaled download code has been expanded to use ARB_get_texture_sub_image when possible
  which is faster and more convenient than glReadPixels. The latter is still relevant for OpenGLES though.

* Finally allocations are now given a handy debug name that can be viewed from renderdoc.

* rasterizer_cache: Remove global state

* gl_rasterizer: Abstract common draw operations to Framebuffer

* This also allows to cache framebuffer objects instead of always swapping the textures, something that particularly benefits mali gpus

* rasterizer_cache: Implement multi-level surfaces

* With this commit the cache can now directly upload and use mipmaps
  without needing to sync them with watchers. By using native mimaps
  directly this also adds support for mipmap for cube

* Texture cubes have also been updated to drop the watcher requirement

* host_shaders: Add CMake integration for string shaders

* Improves build time shader generation making it much less prone to errors.
  Also moves the presentation shaders here to avoid embedding them to the cpp file.

* Texture filter shaders now make explicit use of uniform bindings for better vulkan compatibility

* renderer_opengl: Emulate lod bias in the shader

* This way opengles can emulate it correctly

* gl_rasterizer: Respect GL_MAX_TEXTURE_BUFFER_SIZE

* Older Bifrost Mali GPUs only support up to 64kb texture buffers. Citra would try to allocate a much larger buffer the first 64kb of which would work fine but after that the driver starts misbehaving and showing various graphical glitches

* rasterizer_cache: Cleanup CopySurface

* renderer_opengl: Keep frames synchronized when using a GPU debugger

* rasterizer_cache: Rename Surface to SurfaceRef

* Makes it clear that surface is a shared_ptr and not an object

* rasterizer_cache: Cleanup

* Move constructor to the top of the file

* Move FindMatch to the top as well and remove the Invalid flag which was redudant;
  all FindMatch calls used it expect from MatchFlags::Copy which ignores it anyway

* gl_texture_runtime: Make driver const

* gl_texture_runtime: Fix RGB8 format handling

* The texture_codec header, being written with vulkan in mind converts RGB8 to RGBA8. The backend wasn't adjusted to account for this though and treated the data as RGB8.

* Also remove D16 convertions, both opengl and vulkan are required to support this format so these are not needed

* gl_texture_runtime: Reduce state switches during FBO blits

* glBlitFramebuffer is only affected by the scissor rectangle so just disable scissor testing instead of resetting our entire state

* surface_params: Prevent texcopy that spans multiple levels

* It would have failed before as well, with multi-level surfaces it triggers the assert though

* renderer_opengl: Centralize texture filters

* A lot of code is shared between the filters thus is makes it sense to centralize them

* Also fix an issue with partial texture uploads

* Address review comments

* rasterizer_cache: Use leading return types

* rasterizer_cache: Cleanup null checks

* renderer_opengl: Add additional logging

* externals: Actually downgrade glad

* For some reason I missed adding the files to git

* surface_params: Do not check for levels in exact match

* Some games will try to use the base level of a multi level surface. Checking for levels forces another surface to be created and a copy to be made which is both unncessary and breaks custom textures

---------

Co-authored-by: bunnei <bunneidev@gmail.com>
This commit is contained in:
GPUCode 2023-04-21 10:14:55 +03:00 committed by GitHub
parent 9414db4f65
commit 26d6f9d1c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 4606 additions and 4984 deletions

View file

@ -1,26 +1,28 @@
/*
OpenGL, OpenGL ES loader generated by glad 0.1.36 on Sun Mar 12 10:25:27 2023.
OpenGL, OpenGL ES loader generated by glad 0.1.36 on Sat Apr 1 20:34:42 2023.
Language/Generator: C/C++
Specification: gl
APIs: gl=4.6, gles2=3.2
APIs: gl=4.3, gles2=3.2
Profile: core
Extensions:
GL_ARB_buffer_storage,
GL_ARB_clear_texture,
GL_ARB_get_texture_sub_image,
GL_ARB_texture_compression_bptc,
GL_EXT_buffer_storage,
GL_EXT_clip_cull_distance
GL_EXT_clip_cull_distance,
GL_EXT_texture_compression_s3tc
Loader: True
Local files: False
Omit khrplatform: False
Reproducible: False
Commandline:
--profile="core" --api="gl=4.6,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_get_texture_sub_image,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance"
--profile="core" --api="gl=4.3,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_get_texture_sub_image,GL_ARB_texture_compression_bptc,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance,GL_EXT_texture_compression_s3tc"
Online:
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.6&api=gles2%3D3.2&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_get_texture_sub_image&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3&api=gles2%3D3.2&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_get_texture_sub_image&extensions=GL_ARB_texture_compression_bptc&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance&extensions=GL_EXT_texture_compression_s3tc
*/
@ -1454,81 +1456,6 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
#define GL_DISPLAY_LIST 0x82E7
#define GL_STACK_UNDERFLOW 0x0504
#define GL_STACK_OVERFLOW 0x0503
#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5
#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221
#define GL_TEXTURE_BUFFER_BINDING 0x8C2A
#define GL_MAP_PERSISTENT_BIT 0x0040
#define GL_MAP_COHERENT_BIT 0x0080
#define GL_DYNAMIC_STORAGE_BIT 0x0100
#define GL_CLIENT_STORAGE_BIT 0x0200
#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000
#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F
#define GL_BUFFER_STORAGE_FLAGS 0x8220
#define GL_CLEAR_TEXTURE 0x9365
#define GL_LOCATION_COMPONENT 0x934A
#define GL_TRANSFORM_FEEDBACK_BUFFER_INDEX 0x934B
#define GL_TRANSFORM_FEEDBACK_BUFFER_STRIDE 0x934C
#define GL_QUERY_BUFFER 0x9192
#define GL_QUERY_BUFFER_BARRIER_BIT 0x00008000
#define GL_QUERY_BUFFER_BINDING 0x9193
#define GL_QUERY_RESULT_NO_WAIT 0x9194
#define GL_MIRROR_CLAMP_TO_EDGE 0x8743
#define GL_CONTEXT_LOST 0x0507
#define GL_NEGATIVE_ONE_TO_ONE 0x935E
#define GL_ZERO_TO_ONE 0x935F
#define GL_CLIP_ORIGIN 0x935C
#define GL_CLIP_DEPTH_MODE 0x935D
#define GL_QUERY_WAIT_INVERTED 0x8E17
#define GL_QUERY_NO_WAIT_INVERTED 0x8E18
#define GL_QUERY_BY_REGION_WAIT_INVERTED 0x8E19
#define GL_QUERY_BY_REGION_NO_WAIT_INVERTED 0x8E1A
#define GL_MAX_CULL_DISTANCES 0x82F9
#define GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES 0x82FA
#define GL_TEXTURE_TARGET 0x1006
#define GL_QUERY_TARGET 0x82EA
#define GL_GUILTY_CONTEXT_RESET 0x8253
#define GL_INNOCENT_CONTEXT_RESET 0x8254
#define GL_UNKNOWN_CONTEXT_RESET 0x8255
#define GL_RESET_NOTIFICATION_STRATEGY 0x8256
#define GL_LOSE_CONTEXT_ON_RESET 0x8252
#define GL_NO_RESET_NOTIFICATION 0x8261
#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004
#define GL_COLOR_TABLE 0x80D0
#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1
#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2
#define GL_PROXY_COLOR_TABLE 0x80D3
#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4
#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5
#define GL_CONVOLUTION_1D 0x8010
#define GL_CONVOLUTION_2D 0x8011
#define GL_SEPARABLE_2D 0x8012
#define GL_HISTOGRAM 0x8024
#define GL_PROXY_HISTOGRAM 0x8025
#define GL_MINMAX 0x802E
#define GL_CONTEXT_RELEASE_BEHAVIOR 0x82FB
#define GL_CONTEXT_RELEASE_BEHAVIOR_FLUSH 0x82FC
#define GL_SHADER_BINARY_FORMAT_SPIR_V 0x9551
#define GL_SPIR_V_BINARY 0x9552
#define GL_PARAMETER_BUFFER 0x80EE
#define GL_PARAMETER_BUFFER_BINDING 0x80EF
#define GL_CONTEXT_FLAG_NO_ERROR_BIT 0x00000008
#define GL_VERTICES_SUBMITTED 0x82EE
#define GL_PRIMITIVES_SUBMITTED 0x82EF
#define GL_VERTEX_SHADER_INVOCATIONS 0x82F0
#define GL_TESS_CONTROL_SHADER_PATCHES 0x82F1
#define GL_TESS_EVALUATION_SHADER_INVOCATIONS 0x82F2
#define GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED 0x82F3
#define GL_FRAGMENT_SHADER_INVOCATIONS 0x82F4
#define GL_COMPUTE_SHADER_INVOCATIONS 0x82F5
#define GL_CLIPPING_INPUT_PRIMITIVES 0x82F6
#define GL_CLIPPING_OUTPUT_PRIMITIVES 0x82F7
#define GL_POLYGON_OFFSET_CLAMP 0x8E1B
#define GL_SPIR_V_EXTENSIONS 0x9553
#define GL_NUM_SPIR_V_EXTENSIONS 0x9554
#define GL_TEXTURE_MAX_ANISOTROPY 0x84FE
#define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF
#define GL_TRANSFORM_FEEDBACK_OVERFLOW 0x82EC
#define GL_TRANSFORM_FEEDBACK_STREAM_OVERFLOW 0x82ED
#define GL_ALIASED_POINT_SIZE_RANGE 0x846D
#define GL_RED_BITS 0x0D52
#define GL_GREEN_BITS 0x0D53
@ -1540,6 +1467,7 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
#define GL_LUMINANCE 0x1909
#define GL_LUMINANCE_ALPHA 0x190A
#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS 0x8CD9
#define GL_MAX_VERTEX_ATTRIB_STRIDE 0x82E5
#define GL_MULTISAMPLE_LINE_WIDTH_RANGE 0x9381
#define GL_MULTISAMPLE_LINE_WIDTH_GRANULARITY 0x9382
#define GL_MULTIPLY 0x9294
@ -1558,6 +1486,16 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
#define GL_HSL_COLOR 0x92AF
#define GL_HSL_LUMINOSITY 0x92B0
#define GL_PRIMITIVE_BOUNDING_BOX 0x92BE
#define GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT 0x00000004
#define GL_LOSE_CONTEXT_ON_RESET 0x8252
#define GL_GUILTY_CONTEXT_RESET 0x8253
#define GL_INNOCENT_CONTEXT_RESET 0x8254
#define GL_UNKNOWN_CONTEXT_RESET 0x8255
#define GL_RESET_NOTIFICATION_STRATEGY 0x8256
#define GL_NO_RESET_NOTIFICATION 0x8261
#define GL_CONTEXT_LOST 0x0507
#define GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED 0x8221
#define GL_TEXTURE_BUFFER_BINDING 0x8C2A
#define GL_COMPRESSED_RGBA_ASTC_4x4 0x93B0
#define GL_COMPRESSED_RGBA_ASTC_5x4 0x93B1
#define GL_COMPRESSED_RGBA_ASTC_5x5 0x93B2
@ -3342,423 +3280,6 @@ typedef void (APIENTRYP PFNGLGETPOINTERVPROC)(GLenum pname, void **params);
GLAPI PFNGLGETPOINTERVPROC glad_glGetPointerv;
#define glGetPointerv glad_glGetPointerv
#endif
#ifndef GL_VERSION_4_4
#define GL_VERSION_4_4 1
GLAPI int GLAD_GL_VERSION_4_4;
typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, const void *data, GLbitfield flags);
GLAPI PFNGLBUFFERSTORAGEPROC glad_glBufferStorage;
#define glBufferStorage glad_glBufferStorage
typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC)(GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
GLAPI PFNGLCLEARTEXIMAGEPROC glad_glClearTexImage;
#define glClearTexImage glad_glClearTexImage
typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
GLAPI PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage;
#define glClearTexSubImage glad_glClearTexSubImage
typedef void (APIENTRYP PFNGLBINDBUFFERSBASEPROC)(GLenum target, GLuint first, GLsizei count, const GLuint *buffers);
GLAPI PFNGLBINDBUFFERSBASEPROC glad_glBindBuffersBase;
#define glBindBuffersBase glad_glBindBuffersBase
typedef void (APIENTRYP PFNGLBINDBUFFERSRANGEPROC)(GLenum target, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizeiptr *sizes);
GLAPI PFNGLBINDBUFFERSRANGEPROC glad_glBindBuffersRange;
#define glBindBuffersRange glad_glBindBuffersRange
typedef void (APIENTRYP PFNGLBINDTEXTURESPROC)(GLuint first, GLsizei count, const GLuint *textures);
GLAPI PFNGLBINDTEXTURESPROC glad_glBindTextures;
#define glBindTextures glad_glBindTextures
typedef void (APIENTRYP PFNGLBINDSAMPLERSPROC)(GLuint first, GLsizei count, const GLuint *samplers);
GLAPI PFNGLBINDSAMPLERSPROC glad_glBindSamplers;
#define glBindSamplers glad_glBindSamplers
typedef void (APIENTRYP PFNGLBINDIMAGETEXTURESPROC)(GLuint first, GLsizei count, const GLuint *textures);
GLAPI PFNGLBINDIMAGETEXTURESPROC glad_glBindImageTextures;
#define glBindImageTextures glad_glBindImageTextures
typedef void (APIENTRYP PFNGLBINDVERTEXBUFFERSPROC)(GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides);
GLAPI PFNGLBINDVERTEXBUFFERSPROC glad_glBindVertexBuffers;
#define glBindVertexBuffers glad_glBindVertexBuffers
#endif
#ifndef GL_VERSION_4_5
#define GL_VERSION_4_5 1
GLAPI int GLAD_GL_VERSION_4_5;
typedef void (APIENTRYP PFNGLCLIPCONTROLPROC)(GLenum origin, GLenum depth);
GLAPI PFNGLCLIPCONTROLPROC glad_glClipControl;
#define glClipControl glad_glClipControl
typedef void (APIENTRYP PFNGLCREATETRANSFORMFEEDBACKSPROC)(GLsizei n, GLuint *ids);
GLAPI PFNGLCREATETRANSFORMFEEDBACKSPROC glad_glCreateTransformFeedbacks;
#define glCreateTransformFeedbacks glad_glCreateTransformFeedbacks
typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC)(GLuint xfb, GLuint index, GLuint buffer);
GLAPI PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC glad_glTransformFeedbackBufferBase;
#define glTransformFeedbackBufferBase glad_glTransformFeedbackBufferBase
typedef void (APIENTRYP PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC)(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
GLAPI PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC glad_glTransformFeedbackBufferRange;
#define glTransformFeedbackBufferRange glad_glTransformFeedbackBufferRange
typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKIVPROC)(GLuint xfb, GLenum pname, GLint *param);
GLAPI PFNGLGETTRANSFORMFEEDBACKIVPROC glad_glGetTransformFeedbackiv;
#define glGetTransformFeedbackiv glad_glGetTransformFeedbackiv
typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI_VPROC)(GLuint xfb, GLenum pname, GLuint index, GLint *param);
GLAPI PFNGLGETTRANSFORMFEEDBACKI_VPROC glad_glGetTransformFeedbacki_v;
#define glGetTransformFeedbacki_v glad_glGetTransformFeedbacki_v
typedef void (APIENTRYP PFNGLGETTRANSFORMFEEDBACKI64_VPROC)(GLuint xfb, GLenum pname, GLuint index, GLint64 *param);
GLAPI PFNGLGETTRANSFORMFEEDBACKI64_VPROC glad_glGetTransformFeedbacki64_v;
#define glGetTransformFeedbacki64_v glad_glGetTransformFeedbacki64_v
typedef void (APIENTRYP PFNGLCREATEBUFFERSPROC)(GLsizei n, GLuint *buffers);
GLAPI PFNGLCREATEBUFFERSPROC glad_glCreateBuffers;
#define glCreateBuffers glad_glCreateBuffers
typedef void (APIENTRYP PFNGLNAMEDBUFFERSTORAGEPROC)(GLuint buffer, GLsizeiptr size, const void *data, GLbitfield flags);
GLAPI PFNGLNAMEDBUFFERSTORAGEPROC glad_glNamedBufferStorage;
#define glNamedBufferStorage glad_glNamedBufferStorage
typedef void (APIENTRYP PFNGLNAMEDBUFFERDATAPROC)(GLuint buffer, GLsizeiptr size, const void *data, GLenum usage);
GLAPI PFNGLNAMEDBUFFERDATAPROC glad_glNamedBufferData;
#define glNamedBufferData glad_glNamedBufferData
typedef void (APIENTRYP PFNGLNAMEDBUFFERSUBDATAPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, const void *data);
GLAPI PFNGLNAMEDBUFFERSUBDATAPROC glad_glNamedBufferSubData;
#define glNamedBufferSubData glad_glNamedBufferSubData
typedef void (APIENTRYP PFNGLCOPYNAMEDBUFFERSUBDATAPROC)(GLuint readBuffer, GLuint writeBuffer, GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
GLAPI PFNGLCOPYNAMEDBUFFERSUBDATAPROC glad_glCopyNamedBufferSubData;
#define glCopyNamedBufferSubData glad_glCopyNamedBufferSubData
typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERDATAPROC)(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void *data);
GLAPI PFNGLCLEARNAMEDBUFFERDATAPROC glad_glClearNamedBufferData;
#define glClearNamedBufferData glad_glClearNamedBufferData
typedef void (APIENTRYP PFNGLCLEARNAMEDBUFFERSUBDATAPROC)(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, const void *data);
GLAPI PFNGLCLEARNAMEDBUFFERSUBDATAPROC glad_glClearNamedBufferSubData;
#define glClearNamedBufferSubData glad_glClearNamedBufferSubData
typedef void * (APIENTRYP PFNGLMAPNAMEDBUFFERPROC)(GLuint buffer, GLenum access);
GLAPI PFNGLMAPNAMEDBUFFERPROC glad_glMapNamedBuffer;
#define glMapNamedBuffer glad_glMapNamedBuffer
typedef void * (APIENTRYP PFNGLMAPNAMEDBUFFERRANGEPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length, GLbitfield access);
GLAPI PFNGLMAPNAMEDBUFFERRANGEPROC glad_glMapNamedBufferRange;
#define glMapNamedBufferRange glad_glMapNamedBufferRange
typedef GLboolean (APIENTRYP PFNGLUNMAPNAMEDBUFFERPROC)(GLuint buffer);
GLAPI PFNGLUNMAPNAMEDBUFFERPROC glad_glUnmapNamedBuffer;
#define glUnmapNamedBuffer glad_glUnmapNamedBuffer
typedef void (APIENTRYP PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC)(GLuint buffer, GLintptr offset, GLsizeiptr length);
GLAPI PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC glad_glFlushMappedNamedBufferRange;
#define glFlushMappedNamedBufferRange glad_glFlushMappedNamedBufferRange
typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERIVPROC)(GLuint buffer, GLenum pname, GLint *params);
GLAPI PFNGLGETNAMEDBUFFERPARAMETERIVPROC glad_glGetNamedBufferParameteriv;
#define glGetNamedBufferParameteriv glad_glGetNamedBufferParameteriv
typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPARAMETERI64VPROC)(GLuint buffer, GLenum pname, GLint64 *params);
GLAPI PFNGLGETNAMEDBUFFERPARAMETERI64VPROC glad_glGetNamedBufferParameteri64v;
#define glGetNamedBufferParameteri64v glad_glGetNamedBufferParameteri64v
typedef void (APIENTRYP PFNGLGETNAMEDBUFFERPOINTERVPROC)(GLuint buffer, GLenum pname, void **params);
GLAPI PFNGLGETNAMEDBUFFERPOINTERVPROC glad_glGetNamedBufferPointerv;
#define glGetNamedBufferPointerv glad_glGetNamedBufferPointerv
typedef void (APIENTRYP PFNGLGETNAMEDBUFFERSUBDATAPROC)(GLuint buffer, GLintptr offset, GLsizeiptr size, void *data);
GLAPI PFNGLGETNAMEDBUFFERSUBDATAPROC glad_glGetNamedBufferSubData;
#define glGetNamedBufferSubData glad_glGetNamedBufferSubData
typedef void (APIENTRYP PFNGLCREATEFRAMEBUFFERSPROC)(GLsizei n, GLuint *framebuffers);
GLAPI PFNGLCREATEFRAMEBUFFERSPROC glad_glCreateFramebuffers;
#define glCreateFramebuffers glad_glCreateFramebuffers
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC)(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
GLAPI PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC glad_glNamedFramebufferRenderbuffer;
#define glNamedFramebufferRenderbuffer glad_glNamedFramebufferRenderbuffer
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC)(GLuint framebuffer, GLenum pname, GLint param);
GLAPI PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC glad_glNamedFramebufferParameteri;
#define glNamedFramebufferParameteri glad_glNamedFramebufferParameteri
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTUREPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level);
GLAPI PFNGLNAMEDFRAMEBUFFERTEXTUREPROC glad_glNamedFramebufferTexture;
#define glNamedFramebufferTexture glad_glNamedFramebufferTexture
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC)(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer);
GLAPI PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC glad_glNamedFramebufferTextureLayer;
#define glNamedFramebufferTextureLayer glad_glNamedFramebufferTextureLayer
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC)(GLuint framebuffer, GLenum buf);
GLAPI PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC glad_glNamedFramebufferDrawBuffer;
#define glNamedFramebufferDrawBuffer glad_glNamedFramebufferDrawBuffer
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC)(GLuint framebuffer, GLsizei n, const GLenum *bufs);
GLAPI PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC glad_glNamedFramebufferDrawBuffers;
#define glNamedFramebufferDrawBuffers glad_glNamedFramebufferDrawBuffers
typedef void (APIENTRYP PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC)(GLuint framebuffer, GLenum src);
GLAPI PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC glad_glNamedFramebufferReadBuffer;
#define glNamedFramebufferReadBuffer glad_glNamedFramebufferReadBuffer
typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC)(GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments);
GLAPI PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC glad_glInvalidateNamedFramebufferData;
#define glInvalidateNamedFramebufferData glad_glInvalidateNamedFramebufferData
typedef void (APIENTRYP PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC)(GLuint framebuffer, GLsizei numAttachments, const GLenum *attachments, GLint x, GLint y, GLsizei width, GLsizei height);
GLAPI PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC glad_glInvalidateNamedFramebufferSubData;
#define glInvalidateNamedFramebufferSubData glad_glInvalidateNamedFramebufferSubData
typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERIVPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLint *value);
GLAPI PFNGLCLEARNAMEDFRAMEBUFFERIVPROC glad_glClearNamedFramebufferiv;
#define glClearNamedFramebufferiv glad_glClearNamedFramebufferiv
typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLuint *value);
GLAPI PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC glad_glClearNamedFramebufferuiv;
#define glClearNamedFramebufferuiv glad_glClearNamedFramebufferuiv
typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFVPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, const GLfloat *value);
GLAPI PFNGLCLEARNAMEDFRAMEBUFFERFVPROC glad_glClearNamedFramebufferfv;
#define glClearNamedFramebufferfv glad_glClearNamedFramebufferfv
typedef void (APIENTRYP PFNGLCLEARNAMEDFRAMEBUFFERFIPROC)(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
GLAPI PFNGLCLEARNAMEDFRAMEBUFFERFIPROC glad_glClearNamedFramebufferfi;
#define glClearNamedFramebufferfi glad_glClearNamedFramebufferfi
typedef void (APIENTRYP PFNGLBLITNAMEDFRAMEBUFFERPROC)(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
GLAPI PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer;
#define glBlitNamedFramebuffer glad_glBlitNamedFramebuffer
typedef GLenum (APIENTRYP PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC)(GLuint framebuffer, GLenum target);
GLAPI PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC glad_glCheckNamedFramebufferStatus;
#define glCheckNamedFramebufferStatus glad_glCheckNamedFramebufferStatus
typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC)(GLuint framebuffer, GLenum pname, GLint *param);
GLAPI PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC glad_glGetNamedFramebufferParameteriv;
#define glGetNamedFramebufferParameteriv glad_glGetNamedFramebufferParameteriv
typedef void (APIENTRYP PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC)(GLuint framebuffer, GLenum attachment, GLenum pname, GLint *params);
GLAPI PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetNamedFramebufferAttachmentParameteriv;
#define glGetNamedFramebufferAttachmentParameteriv glad_glGetNamedFramebufferAttachmentParameteriv
typedef void (APIENTRYP PFNGLCREATERENDERBUFFERSPROC)(GLsizei n, GLuint *renderbuffers);
GLAPI PFNGLCREATERENDERBUFFERSPROC glad_glCreateRenderbuffers;
#define glCreateRenderbuffers glad_glCreateRenderbuffers
typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEPROC)(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height);
GLAPI PFNGLNAMEDRENDERBUFFERSTORAGEPROC glad_glNamedRenderbufferStorage;
#define glNamedRenderbufferStorage glad_glNamedRenderbufferStorage
typedef void (APIENTRYP PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC)(GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height);
GLAPI PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glNamedRenderbufferStorageMultisample;
#define glNamedRenderbufferStorageMultisample glad_glNamedRenderbufferStorageMultisample
typedef void (APIENTRYP PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC)(GLuint renderbuffer, GLenum pname, GLint *params);
GLAPI PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC glad_glGetNamedRenderbufferParameteriv;
#define glGetNamedRenderbufferParameteriv glad_glGetNamedRenderbufferParameteriv
typedef void (APIENTRYP PFNGLCREATETEXTURESPROC)(GLenum target, GLsizei n, GLuint *textures);
GLAPI PFNGLCREATETEXTURESPROC glad_glCreateTextures;
#define glCreateTextures glad_glCreateTextures
typedef void (APIENTRYP PFNGLTEXTUREBUFFERPROC)(GLuint texture, GLenum internalformat, GLuint buffer);
GLAPI PFNGLTEXTUREBUFFERPROC glad_glTextureBuffer;
#define glTextureBuffer glad_glTextureBuffer
typedef void (APIENTRYP PFNGLTEXTUREBUFFERRANGEPROC)(GLuint texture, GLenum internalformat, GLuint buffer, GLintptr offset, GLsizeiptr size);
GLAPI PFNGLTEXTUREBUFFERRANGEPROC glad_glTextureBufferRange;
#define glTextureBufferRange glad_glTextureBufferRange
typedef void (APIENTRYP PFNGLTEXTURESTORAGE1DPROC)(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width);
GLAPI PFNGLTEXTURESTORAGE1DPROC glad_glTextureStorage1D;
#define glTextureStorage1D glad_glTextureStorage1D
typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DPROC)(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
GLAPI PFNGLTEXTURESTORAGE2DPROC glad_glTextureStorage2D;
#define glTextureStorage2D glad_glTextureStorage2D
typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DPROC)(GLuint texture, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
GLAPI PFNGLTEXTURESTORAGE3DPROC glad_glTextureStorage3D;
#define glTextureStorage3D glad_glTextureStorage3D
typedef void (APIENTRYP PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC)(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations);
GLAPI PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC glad_glTextureStorage2DMultisample;
#define glTextureStorage2DMultisample glad_glTextureStorage2DMultisample
typedef void (APIENTRYP PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC)(GLuint texture, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations);
GLAPI PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC glad_glTextureStorage3DMultisample;
#define glTextureStorage3DMultisample glad_glTextureStorage3DMultisample
typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE1DPROC)(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void *pixels);
GLAPI PFNGLTEXTURESUBIMAGE1DPROC glad_glTextureSubImage1D;
#define glTextureSubImage1D glad_glTextureSubImage1D
typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE2DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
GLAPI PFNGLTEXTURESUBIMAGE2DPROC glad_glTextureSubImage2D;
#define glTextureSubImage2D glad_glTextureSubImage2D
typedef void (APIENTRYP PFNGLTEXTURESUBIMAGE3DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels);
GLAPI PFNGLTEXTURESUBIMAGE3DPROC glad_glTextureSubImage3D;
#define glTextureSubImage3D glad_glTextureSubImage3D
typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC)(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data);
GLAPI PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC glad_glCompressedTextureSubImage1D;
#define glCompressedTextureSubImage1D glad_glCompressedTextureSubImage1D
typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data);
GLAPI PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC glad_glCompressedTextureSubImage2D;
#define glCompressedTextureSubImage2D glad_glCompressedTextureSubImage2D
typedef void (APIENTRYP PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data);
GLAPI PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC glad_glCompressedTextureSubImage3D;
#define glCompressedTextureSubImage3D glad_glCompressedTextureSubImage3D
typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE1DPROC)(GLuint texture, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
GLAPI PFNGLCOPYTEXTURESUBIMAGE1DPROC glad_glCopyTextureSubImage1D;
#define glCopyTextureSubImage1D glad_glCopyTextureSubImage1D
typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE2DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
GLAPI PFNGLCOPYTEXTURESUBIMAGE2DPROC glad_glCopyTextureSubImage2D;
#define glCopyTextureSubImage2D glad_glCopyTextureSubImage2D
typedef void (APIENTRYP PFNGLCOPYTEXTURESUBIMAGE3DPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);
GLAPI PFNGLCOPYTEXTURESUBIMAGE3DPROC glad_glCopyTextureSubImage3D;
#define glCopyTextureSubImage3D glad_glCopyTextureSubImage3D
typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFPROC)(GLuint texture, GLenum pname, GLfloat param);
GLAPI PFNGLTEXTUREPARAMETERFPROC glad_glTextureParameterf;
#define glTextureParameterf glad_glTextureParameterf
typedef void (APIENTRYP PFNGLTEXTUREPARAMETERFVPROC)(GLuint texture, GLenum pname, const GLfloat *param);
GLAPI PFNGLTEXTUREPARAMETERFVPROC glad_glTextureParameterfv;
#define glTextureParameterfv glad_glTextureParameterfv
typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIPROC)(GLuint texture, GLenum pname, GLint param);
GLAPI PFNGLTEXTUREPARAMETERIPROC glad_glTextureParameteri;
#define glTextureParameteri glad_glTextureParameteri
typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIIVPROC)(GLuint texture, GLenum pname, const GLint *params);
GLAPI PFNGLTEXTUREPARAMETERIIVPROC glad_glTextureParameterIiv;
#define glTextureParameterIiv glad_glTextureParameterIiv
typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIUIVPROC)(GLuint texture, GLenum pname, const GLuint *params);
GLAPI PFNGLTEXTUREPARAMETERIUIVPROC glad_glTextureParameterIuiv;
#define glTextureParameterIuiv glad_glTextureParameterIuiv
typedef void (APIENTRYP PFNGLTEXTUREPARAMETERIVPROC)(GLuint texture, GLenum pname, const GLint *param);
GLAPI PFNGLTEXTUREPARAMETERIVPROC glad_glTextureParameteriv;
#define glTextureParameteriv glad_glTextureParameteriv
typedef void (APIENTRYP PFNGLGENERATETEXTUREMIPMAPPROC)(GLuint texture);
GLAPI PFNGLGENERATETEXTUREMIPMAPPROC glad_glGenerateTextureMipmap;
#define glGenerateTextureMipmap glad_glGenerateTextureMipmap
typedef void (APIENTRYP PFNGLBINDTEXTUREUNITPROC)(GLuint unit, GLuint texture);
GLAPI PFNGLBINDTEXTUREUNITPROC glad_glBindTextureUnit;
#define glBindTextureUnit glad_glBindTextureUnit
typedef void (APIENTRYP PFNGLGETTEXTUREIMAGEPROC)(GLuint texture, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETTEXTUREIMAGEPROC glad_glGetTextureImage;
#define glGetTextureImage glad_glGetTextureImage
typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC)(GLuint texture, GLint level, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC glad_glGetCompressedTextureImage;
#define glGetCompressedTextureImage glad_glGetCompressedTextureImage
typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERFVPROC)(GLuint texture, GLint level, GLenum pname, GLfloat *params);
GLAPI PFNGLGETTEXTURELEVELPARAMETERFVPROC glad_glGetTextureLevelParameterfv;
#define glGetTextureLevelParameterfv glad_glGetTextureLevelParameterfv
typedef void (APIENTRYP PFNGLGETTEXTURELEVELPARAMETERIVPROC)(GLuint texture, GLint level, GLenum pname, GLint *params);
GLAPI PFNGLGETTEXTURELEVELPARAMETERIVPROC glad_glGetTextureLevelParameteriv;
#define glGetTextureLevelParameteriv glad_glGetTextureLevelParameteriv
typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERFVPROC)(GLuint texture, GLenum pname, GLfloat *params);
GLAPI PFNGLGETTEXTUREPARAMETERFVPROC glad_glGetTextureParameterfv;
#define glGetTextureParameterfv glad_glGetTextureParameterfv
typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIIVPROC)(GLuint texture, GLenum pname, GLint *params);
GLAPI PFNGLGETTEXTUREPARAMETERIIVPROC glad_glGetTextureParameterIiv;
#define glGetTextureParameterIiv glad_glGetTextureParameterIiv
typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIUIVPROC)(GLuint texture, GLenum pname, GLuint *params);
GLAPI PFNGLGETTEXTUREPARAMETERIUIVPROC glad_glGetTextureParameterIuiv;
#define glGetTextureParameterIuiv glad_glGetTextureParameterIuiv
typedef void (APIENTRYP PFNGLGETTEXTUREPARAMETERIVPROC)(GLuint texture, GLenum pname, GLint *params);
GLAPI PFNGLGETTEXTUREPARAMETERIVPROC glad_glGetTextureParameteriv;
#define glGetTextureParameteriv glad_glGetTextureParameteriv
typedef void (APIENTRYP PFNGLCREATEVERTEXARRAYSPROC)(GLsizei n, GLuint *arrays);
GLAPI PFNGLCREATEVERTEXARRAYSPROC glad_glCreateVertexArrays;
#define glCreateVertexArrays glad_glCreateVertexArrays
typedef void (APIENTRYP PFNGLDISABLEVERTEXARRAYATTRIBPROC)(GLuint vaobj, GLuint index);
GLAPI PFNGLDISABLEVERTEXARRAYATTRIBPROC glad_glDisableVertexArrayAttrib;
#define glDisableVertexArrayAttrib glad_glDisableVertexArrayAttrib
typedef void (APIENTRYP PFNGLENABLEVERTEXARRAYATTRIBPROC)(GLuint vaobj, GLuint index);
GLAPI PFNGLENABLEVERTEXARRAYATTRIBPROC glad_glEnableVertexArrayAttrib;
#define glEnableVertexArrayAttrib glad_glEnableVertexArrayAttrib
typedef void (APIENTRYP PFNGLVERTEXARRAYELEMENTBUFFERPROC)(GLuint vaobj, GLuint buffer);
GLAPI PFNGLVERTEXARRAYELEMENTBUFFERPROC glad_glVertexArrayElementBuffer;
#define glVertexArrayElementBuffer glad_glVertexArrayElementBuffer
typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERPROC)(GLuint vaobj, GLuint bindingindex, GLuint buffer, GLintptr offset, GLsizei stride);
GLAPI PFNGLVERTEXARRAYVERTEXBUFFERPROC glad_glVertexArrayVertexBuffer;
#define glVertexArrayVertexBuffer glad_glVertexArrayVertexBuffer
typedef void (APIENTRYP PFNGLVERTEXARRAYVERTEXBUFFERSPROC)(GLuint vaobj, GLuint first, GLsizei count, const GLuint *buffers, const GLintptr *offsets, const GLsizei *strides);
GLAPI PFNGLVERTEXARRAYVERTEXBUFFERSPROC glad_glVertexArrayVertexBuffers;
#define glVertexArrayVertexBuffers glad_glVertexArrayVertexBuffers
typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBBINDINGPROC)(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
GLAPI PFNGLVERTEXARRAYATTRIBBINDINGPROC glad_glVertexArrayAttribBinding;
#define glVertexArrayAttribBinding glad_glVertexArrayAttribBinding
typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBFORMATPROC)(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
GLAPI PFNGLVERTEXARRAYATTRIBFORMATPROC glad_glVertexArrayAttribFormat;
#define glVertexArrayAttribFormat glad_glVertexArrayAttribFormat
typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBIFORMATPROC)(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
GLAPI PFNGLVERTEXARRAYATTRIBIFORMATPROC glad_glVertexArrayAttribIFormat;
#define glVertexArrayAttribIFormat glad_glVertexArrayAttribIFormat
typedef void (APIENTRYP PFNGLVERTEXARRAYATTRIBLFORMATPROC)(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
GLAPI PFNGLVERTEXARRAYATTRIBLFORMATPROC glad_glVertexArrayAttribLFormat;
#define glVertexArrayAttribLFormat glad_glVertexArrayAttribLFormat
typedef void (APIENTRYP PFNGLVERTEXARRAYBINDINGDIVISORPROC)(GLuint vaobj, GLuint bindingindex, GLuint divisor);
GLAPI PFNGLVERTEXARRAYBINDINGDIVISORPROC glad_glVertexArrayBindingDivisor;
#define glVertexArrayBindingDivisor glad_glVertexArrayBindingDivisor
typedef void (APIENTRYP PFNGLGETVERTEXARRAYIVPROC)(GLuint vaobj, GLenum pname, GLint *param);
GLAPI PFNGLGETVERTEXARRAYIVPROC glad_glGetVertexArrayiv;
#define glGetVertexArrayiv glad_glGetVertexArrayiv
typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXEDIVPROC)(GLuint vaobj, GLuint index, GLenum pname, GLint *param);
GLAPI PFNGLGETVERTEXARRAYINDEXEDIVPROC glad_glGetVertexArrayIndexediv;
#define glGetVertexArrayIndexediv glad_glGetVertexArrayIndexediv
typedef void (APIENTRYP PFNGLGETVERTEXARRAYINDEXED64IVPROC)(GLuint vaobj, GLuint index, GLenum pname, GLint64 *param);
GLAPI PFNGLGETVERTEXARRAYINDEXED64IVPROC glad_glGetVertexArrayIndexed64iv;
#define glGetVertexArrayIndexed64iv glad_glGetVertexArrayIndexed64iv
typedef void (APIENTRYP PFNGLCREATESAMPLERSPROC)(GLsizei n, GLuint *samplers);
GLAPI PFNGLCREATESAMPLERSPROC glad_glCreateSamplers;
#define glCreateSamplers glad_glCreateSamplers
typedef void (APIENTRYP PFNGLCREATEPROGRAMPIPELINESPROC)(GLsizei n, GLuint *pipelines);
GLAPI PFNGLCREATEPROGRAMPIPELINESPROC glad_glCreateProgramPipelines;
#define glCreateProgramPipelines glad_glCreateProgramPipelines
typedef void (APIENTRYP PFNGLCREATEQUERIESPROC)(GLenum target, GLsizei n, GLuint *ids);
GLAPI PFNGLCREATEQUERIESPROC glad_glCreateQueries;
#define glCreateQueries glad_glCreateQueries
typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTI64VPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
GLAPI PFNGLGETQUERYBUFFEROBJECTI64VPROC glad_glGetQueryBufferObjecti64v;
#define glGetQueryBufferObjecti64v glad_glGetQueryBufferObjecti64v
typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTIVPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
GLAPI PFNGLGETQUERYBUFFEROBJECTIVPROC glad_glGetQueryBufferObjectiv;
#define glGetQueryBufferObjectiv glad_glGetQueryBufferObjectiv
typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUI64VPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
GLAPI PFNGLGETQUERYBUFFEROBJECTUI64VPROC glad_glGetQueryBufferObjectui64v;
#define glGetQueryBufferObjectui64v glad_glGetQueryBufferObjectui64v
typedef void (APIENTRYP PFNGLGETQUERYBUFFEROBJECTUIVPROC)(GLuint id, GLuint buffer, GLenum pname, GLintptr offset);
GLAPI PFNGLGETQUERYBUFFEROBJECTUIVPROC glad_glGetQueryBufferObjectuiv;
#define glGetQueryBufferObjectuiv glad_glGetQueryBufferObjectuiv
typedef void (APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC)(GLbitfield barriers);
GLAPI PFNGLMEMORYBARRIERBYREGIONPROC glad_glMemoryBarrierByRegion;
#define glMemoryBarrierByRegion glad_glMemoryBarrierByRegion
typedef void (APIENTRYP PFNGLGETTEXTURESUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETTEXTURESUBIMAGEPROC glad_glGetTextureSubImage;
#define glGetTextureSubImage glad_glGetTextureSubImage
typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC glad_glGetCompressedTextureSubImage;
#define glGetCompressedTextureSubImage glad_glGetCompressedTextureSubImage
typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSPROC)(void);
GLAPI PFNGLGETGRAPHICSRESETSTATUSPROC glad_glGetGraphicsResetStatus;
#define glGetGraphicsResetStatus glad_glGetGraphicsResetStatus
typedef void (APIENTRYP PFNGLGETNCOMPRESSEDTEXIMAGEPROC)(GLenum target, GLint lod, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETNCOMPRESSEDTEXIMAGEPROC glad_glGetnCompressedTexImage;
#define glGetnCompressedTexImage glad_glGetnCompressedTexImage
typedef void (APIENTRYP PFNGLGETNTEXIMAGEPROC)(GLenum target, GLint level, GLenum format, GLenum type, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETNTEXIMAGEPROC glad_glGetnTexImage;
#define glGetnTexImage glad_glGetnTexImage
typedef void (APIENTRYP PFNGLGETNUNIFORMDVPROC)(GLuint program, GLint location, GLsizei bufSize, GLdouble *params);
GLAPI PFNGLGETNUNIFORMDVPROC glad_glGetnUniformdv;
#define glGetnUniformdv glad_glGetnUniformdv
typedef void (APIENTRYP PFNGLGETNUNIFORMFVPROC)(GLuint program, GLint location, GLsizei bufSize, GLfloat *params);
GLAPI PFNGLGETNUNIFORMFVPROC glad_glGetnUniformfv;
#define glGetnUniformfv glad_glGetnUniformfv
typedef void (APIENTRYP PFNGLGETNUNIFORMIVPROC)(GLuint program, GLint location, GLsizei bufSize, GLint *params);
GLAPI PFNGLGETNUNIFORMIVPROC glad_glGetnUniformiv;
#define glGetnUniformiv glad_glGetnUniformiv
typedef void (APIENTRYP PFNGLGETNUNIFORMUIVPROC)(GLuint program, GLint location, GLsizei bufSize, GLuint *params);
GLAPI PFNGLGETNUNIFORMUIVPROC glad_glGetnUniformuiv;
#define glGetnUniformuiv glad_glGetnUniformuiv
typedef void (APIENTRYP PFNGLREADNPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data);
GLAPI PFNGLREADNPIXELSPROC glad_glReadnPixels;
#define glReadnPixels glad_glReadnPixels
typedef void (APIENTRYP PFNGLGETNMAPDVPROC)(GLenum target, GLenum query, GLsizei bufSize, GLdouble *v);
GLAPI PFNGLGETNMAPDVPROC glad_glGetnMapdv;
#define glGetnMapdv glad_glGetnMapdv
typedef void (APIENTRYP PFNGLGETNMAPFVPROC)(GLenum target, GLenum query, GLsizei bufSize, GLfloat *v);
GLAPI PFNGLGETNMAPFVPROC glad_glGetnMapfv;
#define glGetnMapfv glad_glGetnMapfv
typedef void (APIENTRYP PFNGLGETNMAPIVPROC)(GLenum target, GLenum query, GLsizei bufSize, GLint *v);
GLAPI PFNGLGETNMAPIVPROC glad_glGetnMapiv;
#define glGetnMapiv glad_glGetnMapiv
typedef void (APIENTRYP PFNGLGETNPIXELMAPFVPROC)(GLenum map, GLsizei bufSize, GLfloat *values);
GLAPI PFNGLGETNPIXELMAPFVPROC glad_glGetnPixelMapfv;
#define glGetnPixelMapfv glad_glGetnPixelMapfv
typedef void (APIENTRYP PFNGLGETNPIXELMAPUIVPROC)(GLenum map, GLsizei bufSize, GLuint *values);
GLAPI PFNGLGETNPIXELMAPUIVPROC glad_glGetnPixelMapuiv;
#define glGetnPixelMapuiv glad_glGetnPixelMapuiv
typedef void (APIENTRYP PFNGLGETNPIXELMAPUSVPROC)(GLenum map, GLsizei bufSize, GLushort *values);
GLAPI PFNGLGETNPIXELMAPUSVPROC glad_glGetnPixelMapusv;
#define glGetnPixelMapusv glad_glGetnPixelMapusv
typedef void (APIENTRYP PFNGLGETNPOLYGONSTIPPLEPROC)(GLsizei bufSize, GLubyte *pattern);
GLAPI PFNGLGETNPOLYGONSTIPPLEPROC glad_glGetnPolygonStipple;
#define glGetnPolygonStipple glad_glGetnPolygonStipple
typedef void (APIENTRYP PFNGLGETNCOLORTABLEPROC)(GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *table);
GLAPI PFNGLGETNCOLORTABLEPROC glad_glGetnColorTable;
#define glGetnColorTable glad_glGetnColorTable
typedef void (APIENTRYP PFNGLGETNCONVOLUTIONFILTERPROC)(GLenum target, GLenum format, GLenum type, GLsizei bufSize, void *image);
GLAPI PFNGLGETNCONVOLUTIONFILTERPROC glad_glGetnConvolutionFilter;
#define glGetnConvolutionFilter glad_glGetnConvolutionFilter
typedef void (APIENTRYP PFNGLGETNSEPARABLEFILTERPROC)(GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void *row, GLsizei columnBufSize, void *column, void *span);
GLAPI PFNGLGETNSEPARABLEFILTERPROC glad_glGetnSeparableFilter;
#define glGetnSeparableFilter glad_glGetnSeparableFilter
typedef void (APIENTRYP PFNGLGETNHISTOGRAMPROC)(GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values);
GLAPI PFNGLGETNHISTOGRAMPROC glad_glGetnHistogram;
#define glGetnHistogram glad_glGetnHistogram
typedef void (APIENTRYP PFNGLGETNMINMAXPROC)(GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void *values);
GLAPI PFNGLGETNMINMAXPROC glad_glGetnMinmax;
#define glGetnMinmax glad_glGetnMinmax
typedef void (APIENTRYP PFNGLTEXTUREBARRIERPROC)(void);
GLAPI PFNGLTEXTUREBARRIERPROC glad_glTextureBarrier;
#define glTextureBarrier glad_glTextureBarrier
#endif
#ifndef GL_VERSION_4_6
#define GL_VERSION_4_6 1
GLAPI int GLAD_GL_VERSION_4_6;
typedef void (APIENTRYP PFNGLSPECIALIZESHADERPROC)(GLuint shader, const GLchar *pEntryPoint, GLuint numSpecializationConstants, const GLuint *pConstantIndex, const GLuint *pConstantValue);
GLAPI PFNGLSPECIALIZESHADERPROC glad_glSpecializeShader;
#define glSpecializeShader glad_glSpecializeShader
typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC)(GLenum mode, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
GLAPI PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC glad_glMultiDrawArraysIndirectCount;
#define glMultiDrawArraysIndirectCount glad_glMultiDrawArraysIndirectCount
typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC)(GLenum mode, GLenum type, const void *indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
GLAPI PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC glad_glMultiDrawElementsIndirectCount;
#define glMultiDrawElementsIndirectCount glad_glMultiDrawElementsIndirectCount
typedef void (APIENTRYP PFNGLPOLYGONOFFSETCLAMPPROC)(GLfloat factor, GLfloat units, GLfloat clamp);
GLAPI PFNGLPOLYGONOFFSETCLAMPPROC glad_glPolygonOffsetClamp;
#define glPolygonOffsetClamp glad_glPolygonOffsetClamp
#endif
#ifndef GL_ES_VERSION_2_0
#define GL_ES_VERSION_2_0 1
GLAPI int GLAD_GL_ES_VERSION_2_0;
@ -3770,6 +3291,9 @@ GLAPI int GLAD_GL_ES_VERSION_3_0;
#ifndef GL_ES_VERSION_3_1
#define GL_ES_VERSION_3_1 1
GLAPI int GLAD_GL_ES_VERSION_3_1;
typedef void (APIENTRYP PFNGLMEMORYBARRIERBYREGIONPROC)(GLbitfield barriers);
GLAPI PFNGLMEMORYBARRIERBYREGIONPROC glad_glMemoryBarrierByRegion;
#define glMemoryBarrierByRegion glad_glMemoryBarrierByRegion
#endif
#ifndef GL_ES_VERSION_3_2
#define GL_ES_VERSION_3_2 1
@ -3780,7 +3304,38 @@ GLAPI PFNGLBLENDBARRIERPROC glad_glBlendBarrier;
typedef void (APIENTRYP PFNGLPRIMITIVEBOUNDINGBOXPROC)(GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW);
GLAPI PFNGLPRIMITIVEBOUNDINGBOXPROC glad_glPrimitiveBoundingBox;
#define glPrimitiveBoundingBox glad_glPrimitiveBoundingBox
typedef GLenum (APIENTRYP PFNGLGETGRAPHICSRESETSTATUSPROC)(void);
GLAPI PFNGLGETGRAPHICSRESETSTATUSPROC glad_glGetGraphicsResetStatus;
#define glGetGraphicsResetStatus glad_glGetGraphicsResetStatus
typedef void (APIENTRYP PFNGLREADNPIXELSPROC)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei bufSize, void *data);
GLAPI PFNGLREADNPIXELSPROC glad_glReadnPixels;
#define glReadnPixels glad_glReadnPixels
typedef void (APIENTRYP PFNGLGETNUNIFORMFVPROC)(GLuint program, GLint location, GLsizei bufSize, GLfloat *params);
GLAPI PFNGLGETNUNIFORMFVPROC glad_glGetnUniformfv;
#define glGetnUniformfv glad_glGetnUniformfv
typedef void (APIENTRYP PFNGLGETNUNIFORMIVPROC)(GLuint program, GLint location, GLsizei bufSize, GLint *params);
GLAPI PFNGLGETNUNIFORMIVPROC glad_glGetnUniformiv;
#define glGetnUniformiv glad_glGetnUniformiv
typedef void (APIENTRYP PFNGLGETNUNIFORMUIVPROC)(GLuint program, GLint location, GLsizei bufSize, GLuint *params);
GLAPI PFNGLGETNUNIFORMUIVPROC glad_glGetnUniformuiv;
#define glGetnUniformuiv glad_glGetnUniformuiv
#endif
#define GL_MAP_PERSISTENT_BIT 0x0040
#define GL_MAP_COHERENT_BIT 0x0080
#define GL_DYNAMIC_STORAGE_BIT 0x0100
#define GL_CLIENT_STORAGE_BIT 0x0200
#define GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT 0x00004000
#define GL_BUFFER_IMMUTABLE_STORAGE 0x821F
#define GL_BUFFER_STORAGE_FLAGS 0x8220
#define GL_CLEAR_TEXTURE 0x9365
#define GL_COMPRESSED_RGBA_BPTC_UNORM_ARB 0x8E8C
#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB 0x8E8D
#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB 0x8E8E
#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB 0x8E8F
#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
#define GL_MAP_PERSISTENT_BIT_EXT 0x0040
#define GL_MAP_COHERENT_BIT_EXT 0x0080
#define GL_DYNAMIC_STORAGE_BIT_EXT 0x0100
@ -3802,14 +3357,37 @@ GLAPI PFNGLPRIMITIVEBOUNDINGBOXPROC glad_glPrimitiveBoundingBox;
#ifndef GL_ARB_buffer_storage
#define GL_ARB_buffer_storage 1
GLAPI int GLAD_GL_ARB_buffer_storage;
typedef void (APIENTRYP PFNGLBUFFERSTORAGEPROC)(GLenum target, GLsizeiptr size, const void *data, GLbitfield flags);
GLAPI PFNGLBUFFERSTORAGEPROC glad_glBufferStorage;
#define glBufferStorage glad_glBufferStorage
#endif
#ifndef GL_ARB_clear_texture
#define GL_ARB_clear_texture 1
GLAPI int GLAD_GL_ARB_clear_texture;
typedef void (APIENTRYP PFNGLCLEARTEXIMAGEPROC)(GLuint texture, GLint level, GLenum format, GLenum type, const void *data);
GLAPI PFNGLCLEARTEXIMAGEPROC glad_glClearTexImage;
#define glClearTexImage glad_glClearTexImage
typedef void (APIENTRYP PFNGLCLEARTEXSUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *data);
GLAPI PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage;
#define glClearTexSubImage glad_glClearTexSubImage
#endif
#ifndef GL_ARB_get_texture_sub_image
#define GL_ARB_get_texture_sub_image 1
GLAPI int GLAD_GL_ARB_get_texture_sub_image;
typedef void (APIENTRYP PFNGLGETTEXTURESUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETTEXTURESUBIMAGEPROC glad_glGetTextureSubImage;
#define glGetTextureSubImage glad_glGetTextureSubImage
typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC)(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei bufSize, void *pixels);
GLAPI PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC glad_glGetCompressedTextureSubImage;
#define glGetCompressedTextureSubImage glad_glGetCompressedTextureSubImage
#endif
#ifndef GL_ARB_texture_compression_bptc
#define GL_ARB_texture_compression_bptc 1
GLAPI int GLAD_GL_ARB_texture_compression_bptc;
#endif
#ifndef GL_EXT_texture_compression_s3tc
#define GL_EXT_texture_compression_s3tc 1
GLAPI int GLAD_GL_EXT_texture_compression_s3tc;
#endif
#ifndef GL_EXT_buffer_storage
#define GL_EXT_buffer_storage 1
@ -3822,6 +3400,10 @@ GLAPI PFNGLBUFFERSTORAGEEXTPROC glad_glBufferStorageEXT;
#define GL_EXT_clip_cull_distance 1
GLAPI int GLAD_GL_EXT_clip_cull_distance;
#endif
#ifndef GL_EXT_texture_compression_s3tc
#define GL_EXT_texture_compression_s3tc 1
GLAPI int GLAD_GL_EXT_texture_compression_s3tc;
#endif
#ifdef __cplusplus
}

View file

@ -1,26 +1,28 @@
/*
OpenGL, OpenGL ES loader generated by glad 0.1.36 on Sun Mar 12 10:25:27 2023.
OpenGL, OpenGL ES loader generated by glad 0.1.36 on Sat Apr 1 20:34:42 2023.
Language/Generator: C/C++
Specification: gl
APIs: gl=4.6, gles2=3.2
APIs: gl=4.3, gles2=3.2
Profile: core
Extensions:
GL_ARB_buffer_storage,
GL_ARB_clear_texture,
GL_ARB_get_texture_sub_image,
GL_ARB_texture_compression_bptc,
GL_EXT_buffer_storage,
GL_EXT_clip_cull_distance
GL_EXT_clip_cull_distance,
GL_EXT_texture_compression_s3tc
Loader: True
Local files: False
Omit khrplatform: False
Reproducible: False
Commandline:
--profile="core" --api="gl=4.6,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_get_texture_sub_image,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance"
--profile="core" --api="gl=4.3,gles2=3.2" --generator="c" --spec="gl" --extensions="GL_ARB_buffer_storage,GL_ARB_clear_texture,GL_ARB_get_texture_sub_image,GL_ARB_texture_compression_bptc,GL_EXT_buffer_storage,GL_EXT_clip_cull_distance,GL_EXT_texture_compression_s3tc"
Online:
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.6&api=gles2%3D3.2&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_get_texture_sub_image&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance
https://glad.dav1d.de/#profile=core&language=c&specification=gl&loader=on&api=gl%3D4.3&api=gles2%3D3.2&extensions=GL_ARB_buffer_storage&extensions=GL_ARB_clear_texture&extensions=GL_ARB_get_texture_sub_image&extensions=GL_ARB_texture_compression_bptc&extensions=GL_EXT_buffer_storage&extensions=GL_EXT_clip_cull_distance&extensions=GL_EXT_texture_compression_s3tc
*/
#include <stdio.h>
@ -275,9 +277,6 @@ int GLAD_GL_VERSION_4_0 = 0;
int GLAD_GL_VERSION_4_1 = 0;
int GLAD_GL_VERSION_4_2 = 0;
int GLAD_GL_VERSION_4_3 = 0;
int GLAD_GL_VERSION_4_4 = 0;
int GLAD_GL_VERSION_4_5 = 0;
int GLAD_GL_VERSION_4_6 = 0;
int GLAD_GL_ES_VERSION_2_0 = 0;
int GLAD_GL_ES_VERSION_3_0 = 0;
int GLAD_GL_ES_VERSION_3_1 = 0;
@ -293,24 +292,17 @@ PFNGLBINDATTRIBLOCATIONPROC glad_glBindAttribLocation = NULL;
PFNGLBINDBUFFERPROC glad_glBindBuffer = NULL;
PFNGLBINDBUFFERBASEPROC glad_glBindBufferBase = NULL;
PFNGLBINDBUFFERRANGEPROC glad_glBindBufferRange = NULL;
PFNGLBINDBUFFERSBASEPROC glad_glBindBuffersBase = NULL;
PFNGLBINDBUFFERSRANGEPROC glad_glBindBuffersRange = NULL;
PFNGLBINDFRAGDATALOCATIONPROC glad_glBindFragDataLocation = NULL;
PFNGLBINDFRAGDATALOCATIONINDEXEDPROC glad_glBindFragDataLocationIndexed = NULL;
PFNGLBINDFRAMEBUFFERPROC glad_glBindFramebuffer = NULL;
PFNGLBINDIMAGETEXTUREPROC glad_glBindImageTexture = NULL;
PFNGLBINDIMAGETEXTURESPROC glad_glBindImageTextures = NULL;
PFNGLBINDPROGRAMPIPELINEPROC glad_glBindProgramPipeline = NULL;
PFNGLBINDRENDERBUFFERPROC glad_glBindRenderbuffer = NULL;
PFNGLBINDSAMPLERPROC glad_glBindSampler = NULL;
PFNGLBINDSAMPLERSPROC glad_glBindSamplers = NULL;
PFNGLBINDTEXTUREPROC glad_glBindTexture = NULL;
PFNGLBINDTEXTUREUNITPROC glad_glBindTextureUnit = NULL;
PFNGLBINDTEXTURESPROC glad_glBindTextures = NULL;
PFNGLBINDTRANSFORMFEEDBACKPROC glad_glBindTransformFeedback = NULL;
PFNGLBINDVERTEXARRAYPROC glad_glBindVertexArray = NULL;
PFNGLBINDVERTEXBUFFERPROC glad_glBindVertexBuffer = NULL;
PFNGLBINDVERTEXBUFFERSPROC glad_glBindVertexBuffers = NULL;
PFNGLBLENDBARRIERPROC glad_glBlendBarrier = NULL;
PFNGLBLENDCOLORPROC glad_glBlendColor = NULL;
PFNGLBLENDEQUATIONPROC glad_glBlendEquation = NULL;
@ -322,12 +314,9 @@ PFNGLBLENDFUNCSEPARATEPROC glad_glBlendFuncSeparate = NULL;
PFNGLBLENDFUNCSEPARATEIPROC glad_glBlendFuncSeparatei = NULL;
PFNGLBLENDFUNCIPROC glad_glBlendFunci = NULL;
PFNGLBLITFRAMEBUFFERPROC glad_glBlitFramebuffer = NULL;
PFNGLBLITNAMEDFRAMEBUFFERPROC glad_glBlitNamedFramebuffer = NULL;
PFNGLBUFFERDATAPROC glad_glBufferData = NULL;
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
PFNGLBUFFERSUBDATAPROC glad_glBufferSubData = NULL;
PFNGLCHECKFRAMEBUFFERSTATUSPROC glad_glCheckFramebufferStatus = NULL;
PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC glad_glCheckNamedFramebufferStatus = NULL;
PFNGLCLAMPCOLORPROC glad_glClampColor = NULL;
PFNGLCLEARPROC glad_glClear = NULL;
PFNGLCLEARBUFFERDATAPROC glad_glClearBufferData = NULL;
@ -339,17 +328,8 @@ PFNGLCLEARBUFFERUIVPROC glad_glClearBufferuiv = NULL;
PFNGLCLEARCOLORPROC glad_glClearColor = NULL;
PFNGLCLEARDEPTHPROC glad_glClearDepth = NULL;
PFNGLCLEARDEPTHFPROC glad_glClearDepthf = NULL;
PFNGLCLEARNAMEDBUFFERDATAPROC glad_glClearNamedBufferData = NULL;
PFNGLCLEARNAMEDBUFFERSUBDATAPROC glad_glClearNamedBufferSubData = NULL;
PFNGLCLEARNAMEDFRAMEBUFFERFIPROC glad_glClearNamedFramebufferfi = NULL;
PFNGLCLEARNAMEDFRAMEBUFFERFVPROC glad_glClearNamedFramebufferfv = NULL;
PFNGLCLEARNAMEDFRAMEBUFFERIVPROC glad_glClearNamedFramebufferiv = NULL;
PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC glad_glClearNamedFramebufferuiv = NULL;
PFNGLCLEARSTENCILPROC glad_glClearStencil = NULL;
PFNGLCLEARTEXIMAGEPROC glad_glClearTexImage = NULL;
PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage = NULL;
PFNGLCLIENTWAITSYNCPROC glad_glClientWaitSync = NULL;
PFNGLCLIPCONTROLPROC glad_glClipControl = NULL;
PFNGLCOLORMASKPROC glad_glColorMask = NULL;
PFNGLCOLORMASKIPROC glad_glColorMaski = NULL;
PFNGLCOLORP3UIPROC glad_glColorP3ui = NULL;
@ -363,32 +343,16 @@ PFNGLCOMPRESSEDTEXIMAGE3DPROC glad_glCompressedTexImage3D = NULL;
PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC glad_glCompressedTexSubImage1D = NULL;
PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC glad_glCompressedTexSubImage2D = NULL;
PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC glad_glCompressedTexSubImage3D = NULL;
PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC glad_glCompressedTextureSubImage1D = NULL;
PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC glad_glCompressedTextureSubImage2D = NULL;
PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC glad_glCompressedTextureSubImage3D = NULL;
PFNGLCOPYBUFFERSUBDATAPROC glad_glCopyBufferSubData = NULL;
PFNGLCOPYIMAGESUBDATAPROC glad_glCopyImageSubData = NULL;
PFNGLCOPYNAMEDBUFFERSUBDATAPROC glad_glCopyNamedBufferSubData = NULL;
PFNGLCOPYTEXIMAGE1DPROC glad_glCopyTexImage1D = NULL;
PFNGLCOPYTEXIMAGE2DPROC glad_glCopyTexImage2D = NULL;
PFNGLCOPYTEXSUBIMAGE1DPROC glad_glCopyTexSubImage1D = NULL;
PFNGLCOPYTEXSUBIMAGE2DPROC glad_glCopyTexSubImage2D = NULL;
PFNGLCOPYTEXSUBIMAGE3DPROC glad_glCopyTexSubImage3D = NULL;
PFNGLCOPYTEXTURESUBIMAGE1DPROC glad_glCopyTextureSubImage1D = NULL;
PFNGLCOPYTEXTURESUBIMAGE2DPROC glad_glCopyTextureSubImage2D = NULL;
PFNGLCOPYTEXTURESUBIMAGE3DPROC glad_glCopyTextureSubImage3D = NULL;
PFNGLCREATEBUFFERSPROC glad_glCreateBuffers = NULL;
PFNGLCREATEFRAMEBUFFERSPROC glad_glCreateFramebuffers = NULL;
PFNGLCREATEPROGRAMPROC glad_glCreateProgram = NULL;
PFNGLCREATEPROGRAMPIPELINESPROC glad_glCreateProgramPipelines = NULL;
PFNGLCREATEQUERIESPROC glad_glCreateQueries = NULL;
PFNGLCREATERENDERBUFFERSPROC glad_glCreateRenderbuffers = NULL;
PFNGLCREATESAMPLERSPROC glad_glCreateSamplers = NULL;
PFNGLCREATESHADERPROC glad_glCreateShader = NULL;
PFNGLCREATESHADERPROGRAMVPROC glad_glCreateShaderProgramv = NULL;
PFNGLCREATETEXTURESPROC glad_glCreateTextures = NULL;
PFNGLCREATETRANSFORMFEEDBACKSPROC glad_glCreateTransformFeedbacks = NULL;
PFNGLCREATEVERTEXARRAYSPROC glad_glCreateVertexArrays = NULL;
PFNGLCULLFACEPROC glad_glCullFace = NULL;
PFNGLDEBUGMESSAGECALLBACKPROC glad_glDebugMessageCallback = NULL;
PFNGLDEBUGMESSAGECONTROLPROC glad_glDebugMessageControl = NULL;
@ -413,7 +377,6 @@ PFNGLDEPTHRANGEINDEXEDPROC glad_glDepthRangeIndexed = NULL;
PFNGLDEPTHRANGEFPROC glad_glDepthRangef = NULL;
PFNGLDETACHSHADERPROC glad_glDetachShader = NULL;
PFNGLDISABLEPROC glad_glDisable = NULL;
PFNGLDISABLEVERTEXARRAYATTRIBPROC glad_glDisableVertexArrayAttrib = NULL;
PFNGLDISABLEVERTEXATTRIBARRAYPROC glad_glDisableVertexAttribArray = NULL;
PFNGLDISABLEIPROC glad_glDisablei = NULL;
PFNGLDISPATCHCOMPUTEPROC glad_glDispatchCompute = NULL;
@ -438,7 +401,6 @@ PFNGLDRAWTRANSFORMFEEDBACKINSTANCEDPROC glad_glDrawTransformFeedbackInstanced =
PFNGLDRAWTRANSFORMFEEDBACKSTREAMPROC glad_glDrawTransformFeedbackStream = NULL;
PFNGLDRAWTRANSFORMFEEDBACKSTREAMINSTANCEDPROC glad_glDrawTransformFeedbackStreamInstanced = NULL;
PFNGLENABLEPROC glad_glEnable = NULL;
PFNGLENABLEVERTEXARRAYATTRIBPROC glad_glEnableVertexArrayAttrib = NULL;
PFNGLENABLEVERTEXATTRIBARRAYPROC glad_glEnableVertexAttribArray = NULL;
PFNGLENABLEIPROC glad_glEnablei = NULL;
PFNGLENDCONDITIONALRENDERPROC glad_glEndConditionalRender = NULL;
@ -449,7 +411,6 @@ PFNGLFENCESYNCPROC glad_glFenceSync = NULL;
PFNGLFINISHPROC glad_glFinish = NULL;
PFNGLFLUSHPROC glad_glFlush = NULL;
PFNGLFLUSHMAPPEDBUFFERRANGEPROC glad_glFlushMappedBufferRange = NULL;
PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC glad_glFlushMappedNamedBufferRange = NULL;
PFNGLFRAMEBUFFERPARAMETERIPROC glad_glFramebufferParameteri = NULL;
PFNGLFRAMEBUFFERRENDERBUFFERPROC glad_glFramebufferRenderbuffer = NULL;
PFNGLFRAMEBUFFERTEXTUREPROC glad_glFramebufferTexture = NULL;
@ -468,7 +429,6 @@ PFNGLGENTEXTURESPROC glad_glGenTextures = NULL;
PFNGLGENTRANSFORMFEEDBACKSPROC glad_glGenTransformFeedbacks = NULL;
PFNGLGENVERTEXARRAYSPROC glad_glGenVertexArrays = NULL;
PFNGLGENERATEMIPMAPPROC glad_glGenerateMipmap = NULL;
PFNGLGENERATETEXTUREMIPMAPPROC glad_glGenerateTextureMipmap = NULL;
PFNGLGETACTIVEATOMICCOUNTERBUFFERIVPROC glad_glGetActiveAtomicCounterBufferiv = NULL;
PFNGLGETACTIVEATTRIBPROC glad_glGetActiveAttrib = NULL;
PFNGLGETACTIVESUBROUTINENAMEPROC glad_glGetActiveSubroutineName = NULL;
@ -488,8 +448,6 @@ PFNGLGETBUFFERPARAMETERIVPROC glad_glGetBufferParameteriv = NULL;
PFNGLGETBUFFERPOINTERVPROC glad_glGetBufferPointerv = NULL;
PFNGLGETBUFFERSUBDATAPROC glad_glGetBufferSubData = NULL;
PFNGLGETCOMPRESSEDTEXIMAGEPROC glad_glGetCompressedTexImage = NULL;
PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC glad_glGetCompressedTextureImage = NULL;
PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC glad_glGetCompressedTextureSubImage = NULL;
PFNGLGETDEBUGMESSAGELOGPROC glad_glGetDebugMessageLog = NULL;
PFNGLGETDOUBLEI_VPROC glad_glGetDoublei_v = NULL;
PFNGLGETDOUBLEVPROC glad_glGetDoublev = NULL;
@ -508,13 +466,6 @@ PFNGLGETINTEGERVPROC glad_glGetIntegerv = NULL;
PFNGLGETINTERNALFORMATI64VPROC glad_glGetInternalformati64v = NULL;
PFNGLGETINTERNALFORMATIVPROC glad_glGetInternalformativ = NULL;
PFNGLGETMULTISAMPLEFVPROC glad_glGetMultisamplefv = NULL;
PFNGLGETNAMEDBUFFERPARAMETERI64VPROC glad_glGetNamedBufferParameteri64v = NULL;
PFNGLGETNAMEDBUFFERPARAMETERIVPROC glad_glGetNamedBufferParameteriv = NULL;
PFNGLGETNAMEDBUFFERPOINTERVPROC glad_glGetNamedBufferPointerv = NULL;
PFNGLGETNAMEDBUFFERSUBDATAPROC glad_glGetNamedBufferSubData = NULL;
PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC glad_glGetNamedFramebufferAttachmentParameteriv = NULL;
PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC glad_glGetNamedFramebufferParameteriv = NULL;
PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC glad_glGetNamedRenderbufferParameteriv = NULL;
PFNGLGETOBJECTLABELPROC glad_glGetObjectLabel = NULL;
PFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel = NULL;
PFNGLGETPOINTERVPROC glad_glGetPointerv = NULL;
@ -530,10 +481,6 @@ PFNGLGETPROGRAMRESOURCENAMEPROC glad_glGetProgramResourceName = NULL;
PFNGLGETPROGRAMRESOURCEIVPROC glad_glGetProgramResourceiv = NULL;
PFNGLGETPROGRAMSTAGEIVPROC glad_glGetProgramStageiv = NULL;
PFNGLGETPROGRAMIVPROC glad_glGetProgramiv = NULL;
PFNGLGETQUERYBUFFEROBJECTI64VPROC glad_glGetQueryBufferObjecti64v = NULL;
PFNGLGETQUERYBUFFEROBJECTIVPROC glad_glGetQueryBufferObjectiv = NULL;
PFNGLGETQUERYBUFFEROBJECTUI64VPROC glad_glGetQueryBufferObjectui64v = NULL;
PFNGLGETQUERYBUFFEROBJECTUIVPROC glad_glGetQueryBufferObjectuiv = NULL;
PFNGLGETQUERYINDEXEDIVPROC glad_glGetQueryIndexediv = NULL;
PFNGLGETQUERYOBJECTI64VPROC glad_glGetQueryObjecti64v = NULL;
PFNGLGETQUERYOBJECTIVPROC glad_glGetQueryObjectiv = NULL;
@ -561,18 +508,7 @@ PFNGLGETTEXPARAMETERIIVPROC glad_glGetTexParameterIiv = NULL;
PFNGLGETTEXPARAMETERIUIVPROC glad_glGetTexParameterIuiv = NULL;
PFNGLGETTEXPARAMETERFVPROC glad_glGetTexParameterfv = NULL;
PFNGLGETTEXPARAMETERIVPROC glad_glGetTexParameteriv = NULL;
PFNGLGETTEXTUREIMAGEPROC glad_glGetTextureImage = NULL;
PFNGLGETTEXTURELEVELPARAMETERFVPROC glad_glGetTextureLevelParameterfv = NULL;
PFNGLGETTEXTURELEVELPARAMETERIVPROC glad_glGetTextureLevelParameteriv = NULL;
PFNGLGETTEXTUREPARAMETERIIVPROC glad_glGetTextureParameterIiv = NULL;
PFNGLGETTEXTUREPARAMETERIUIVPROC glad_glGetTextureParameterIuiv = NULL;
PFNGLGETTEXTUREPARAMETERFVPROC glad_glGetTextureParameterfv = NULL;
PFNGLGETTEXTUREPARAMETERIVPROC glad_glGetTextureParameteriv = NULL;
PFNGLGETTEXTURESUBIMAGEPROC glad_glGetTextureSubImage = NULL;
PFNGLGETTRANSFORMFEEDBACKVARYINGPROC glad_glGetTransformFeedbackVarying = NULL;
PFNGLGETTRANSFORMFEEDBACKI64_VPROC glad_glGetTransformFeedbacki64_v = NULL;
PFNGLGETTRANSFORMFEEDBACKI_VPROC glad_glGetTransformFeedbacki_v = NULL;
PFNGLGETTRANSFORMFEEDBACKIVPROC glad_glGetTransformFeedbackiv = NULL;
PFNGLGETUNIFORMBLOCKINDEXPROC glad_glGetUniformBlockIndex = NULL;
PFNGLGETUNIFORMINDICESPROC glad_glGetUniformIndices = NULL;
PFNGLGETUNIFORMLOCATIONPROC glad_glGetUniformLocation = NULL;
@ -581,9 +517,6 @@ PFNGLGETUNIFORMDVPROC glad_glGetUniformdv = NULL;
PFNGLGETUNIFORMFVPROC glad_glGetUniformfv = NULL;
PFNGLGETUNIFORMIVPROC glad_glGetUniformiv = NULL;
PFNGLGETUNIFORMUIVPROC glad_glGetUniformuiv = NULL;
PFNGLGETVERTEXARRAYINDEXED64IVPROC glad_glGetVertexArrayIndexed64iv = NULL;
PFNGLGETVERTEXARRAYINDEXEDIVPROC glad_glGetVertexArrayIndexediv = NULL;
PFNGLGETVERTEXARRAYIVPROC glad_glGetVertexArrayiv = NULL;
PFNGLGETVERTEXATTRIBIIVPROC glad_glGetVertexAttribIiv = NULL;
PFNGLGETVERTEXATTRIBIUIVPROC glad_glGetVertexAttribIuiv = NULL;
PFNGLGETVERTEXATTRIBLDVPROC glad_glGetVertexAttribLdv = NULL;
@ -591,21 +524,6 @@ PFNGLGETVERTEXATTRIBPOINTERVPROC glad_glGetVertexAttribPointerv = NULL;
PFNGLGETVERTEXATTRIBDVPROC glad_glGetVertexAttribdv = NULL;
PFNGLGETVERTEXATTRIBFVPROC glad_glGetVertexAttribfv = NULL;
PFNGLGETVERTEXATTRIBIVPROC glad_glGetVertexAttribiv = NULL;
PFNGLGETNCOLORTABLEPROC glad_glGetnColorTable = NULL;
PFNGLGETNCOMPRESSEDTEXIMAGEPROC glad_glGetnCompressedTexImage = NULL;
PFNGLGETNCONVOLUTIONFILTERPROC glad_glGetnConvolutionFilter = NULL;
PFNGLGETNHISTOGRAMPROC glad_glGetnHistogram = NULL;
PFNGLGETNMAPDVPROC glad_glGetnMapdv = NULL;
PFNGLGETNMAPFVPROC glad_glGetnMapfv = NULL;
PFNGLGETNMAPIVPROC glad_glGetnMapiv = NULL;
PFNGLGETNMINMAXPROC glad_glGetnMinmax = NULL;
PFNGLGETNPIXELMAPFVPROC glad_glGetnPixelMapfv = NULL;
PFNGLGETNPIXELMAPUIVPROC glad_glGetnPixelMapuiv = NULL;
PFNGLGETNPIXELMAPUSVPROC glad_glGetnPixelMapusv = NULL;
PFNGLGETNPOLYGONSTIPPLEPROC glad_glGetnPolygonStipple = NULL;
PFNGLGETNSEPARABLEFILTERPROC glad_glGetnSeparableFilter = NULL;
PFNGLGETNTEXIMAGEPROC glad_glGetnTexImage = NULL;
PFNGLGETNUNIFORMDVPROC glad_glGetnUniformdv = NULL;
PFNGLGETNUNIFORMFVPROC glad_glGetnUniformfv = NULL;
PFNGLGETNUNIFORMIVPROC glad_glGetnUniformiv = NULL;
PFNGLGETNUNIFORMUIVPROC glad_glGetnUniformuiv = NULL;
@ -613,8 +531,6 @@ PFNGLHINTPROC glad_glHint = NULL;
PFNGLINVALIDATEBUFFERDATAPROC glad_glInvalidateBufferData = NULL;
PFNGLINVALIDATEBUFFERSUBDATAPROC glad_glInvalidateBufferSubData = NULL;
PFNGLINVALIDATEFRAMEBUFFERPROC glad_glInvalidateFramebuffer = NULL;
PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC glad_glInvalidateNamedFramebufferData = NULL;
PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC glad_glInvalidateNamedFramebufferSubData = NULL;
PFNGLINVALIDATESUBFRAMEBUFFERPROC glad_glInvalidateSubFramebuffer = NULL;
PFNGLINVALIDATETEXIMAGEPROC glad_glInvalidateTexImage = NULL;
PFNGLINVALIDATETEXSUBIMAGEPROC glad_glInvalidateTexSubImage = NULL;
@ -637,18 +553,14 @@ PFNGLLINKPROGRAMPROC glad_glLinkProgram = NULL;
PFNGLLOGICOPPROC glad_glLogicOp = NULL;
PFNGLMAPBUFFERPROC glad_glMapBuffer = NULL;
PFNGLMAPBUFFERRANGEPROC glad_glMapBufferRange = NULL;
PFNGLMAPNAMEDBUFFERPROC glad_glMapNamedBuffer = NULL;
PFNGLMAPNAMEDBUFFERRANGEPROC glad_glMapNamedBufferRange = NULL;
PFNGLMEMORYBARRIERPROC glad_glMemoryBarrier = NULL;
PFNGLMEMORYBARRIERBYREGIONPROC glad_glMemoryBarrierByRegion = NULL;
PFNGLMINSAMPLESHADINGPROC glad_glMinSampleShading = NULL;
PFNGLMULTIDRAWARRAYSPROC glad_glMultiDrawArrays = NULL;
PFNGLMULTIDRAWARRAYSINDIRECTPROC glad_glMultiDrawArraysIndirect = NULL;
PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC glad_glMultiDrawArraysIndirectCount = NULL;
PFNGLMULTIDRAWELEMENTSPROC glad_glMultiDrawElements = NULL;
PFNGLMULTIDRAWELEMENTSBASEVERTEXPROC glad_glMultiDrawElementsBaseVertex = NULL;
PFNGLMULTIDRAWELEMENTSINDIRECTPROC glad_glMultiDrawElementsIndirect = NULL;
PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC glad_glMultiDrawElementsIndirectCount = NULL;
PFNGLMULTITEXCOORDP1UIPROC glad_glMultiTexCoordP1ui = NULL;
PFNGLMULTITEXCOORDP1UIVPROC glad_glMultiTexCoordP1uiv = NULL;
PFNGLMULTITEXCOORDP2UIPROC glad_glMultiTexCoordP2ui = NULL;
@ -657,18 +569,6 @@ PFNGLMULTITEXCOORDP3UIPROC glad_glMultiTexCoordP3ui = NULL;
PFNGLMULTITEXCOORDP3UIVPROC glad_glMultiTexCoordP3uiv = NULL;
PFNGLMULTITEXCOORDP4UIPROC glad_glMultiTexCoordP4ui = NULL;
PFNGLMULTITEXCOORDP4UIVPROC glad_glMultiTexCoordP4uiv = NULL;
PFNGLNAMEDBUFFERDATAPROC glad_glNamedBufferData = NULL;
PFNGLNAMEDBUFFERSTORAGEPROC glad_glNamedBufferStorage = NULL;
PFNGLNAMEDBUFFERSUBDATAPROC glad_glNamedBufferSubData = NULL;
PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC glad_glNamedFramebufferDrawBuffer = NULL;
PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC glad_glNamedFramebufferDrawBuffers = NULL;
PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC glad_glNamedFramebufferParameteri = NULL;
PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC glad_glNamedFramebufferReadBuffer = NULL;
PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC glad_glNamedFramebufferRenderbuffer = NULL;
PFNGLNAMEDFRAMEBUFFERTEXTUREPROC glad_glNamedFramebufferTexture = NULL;
PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC glad_glNamedFramebufferTextureLayer = NULL;
PFNGLNAMEDRENDERBUFFERSTORAGEPROC glad_glNamedRenderbufferStorage = NULL;
PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC glad_glNamedRenderbufferStorageMultisample = NULL;
PFNGLNORMALP3UIPROC glad_glNormalP3ui = NULL;
PFNGLNORMALP3UIVPROC glad_glNormalP3uiv = NULL;
PFNGLOBJECTLABELPROC glad_glObjectLabel = NULL;
@ -685,7 +585,6 @@ PFNGLPOINTPARAMETERIVPROC glad_glPointParameteriv = NULL;
PFNGLPOINTSIZEPROC glad_glPointSize = NULL;
PFNGLPOLYGONMODEPROC glad_glPolygonMode = NULL;
PFNGLPOLYGONOFFSETPROC glad_glPolygonOffset = NULL;
PFNGLPOLYGONOFFSETCLAMPPROC glad_glPolygonOffsetClamp = NULL;
PFNGLPOPDEBUGGROUPPROC glad_glPopDebugGroup = NULL;
PFNGLPRIMITIVEBOUNDINGBOXPROC glad_glPrimitiveBoundingBox = NULL;
PFNGLPRIMITIVERESTARTINDEXPROC glad_glPrimitiveRestartIndex = NULL;
@ -768,7 +667,6 @@ PFNGLSECONDARYCOLORP3UIVPROC glad_glSecondaryColorP3uiv = NULL;
PFNGLSHADERBINARYPROC glad_glShaderBinary = NULL;
PFNGLSHADERSOURCEPROC glad_glShaderSource = NULL;
PFNGLSHADERSTORAGEBLOCKBINDINGPROC glad_glShaderStorageBlockBinding = NULL;
PFNGLSPECIALIZESHADERPROC glad_glSpecializeShader = NULL;
PFNGLSTENCILFUNCPROC glad_glStencilFunc = NULL;
PFNGLSTENCILFUNCSEPARATEPROC glad_glStencilFuncSeparate = NULL;
PFNGLSTENCILMASKPROC glad_glStencilMask = NULL;
@ -804,26 +702,7 @@ PFNGLTEXSTORAGE3DMULTISAMPLEPROC glad_glTexStorage3DMultisample = NULL;
PFNGLTEXSUBIMAGE1DPROC glad_glTexSubImage1D = NULL;
PFNGLTEXSUBIMAGE2DPROC glad_glTexSubImage2D = NULL;
PFNGLTEXSUBIMAGE3DPROC glad_glTexSubImage3D = NULL;
PFNGLTEXTUREBARRIERPROC glad_glTextureBarrier = NULL;
PFNGLTEXTUREBUFFERPROC glad_glTextureBuffer = NULL;
PFNGLTEXTUREBUFFERRANGEPROC glad_glTextureBufferRange = NULL;
PFNGLTEXTUREPARAMETERIIVPROC glad_glTextureParameterIiv = NULL;
PFNGLTEXTUREPARAMETERIUIVPROC glad_glTextureParameterIuiv = NULL;
PFNGLTEXTUREPARAMETERFPROC glad_glTextureParameterf = NULL;
PFNGLTEXTUREPARAMETERFVPROC glad_glTextureParameterfv = NULL;
PFNGLTEXTUREPARAMETERIPROC glad_glTextureParameteri = NULL;
PFNGLTEXTUREPARAMETERIVPROC glad_glTextureParameteriv = NULL;
PFNGLTEXTURESTORAGE1DPROC glad_glTextureStorage1D = NULL;
PFNGLTEXTURESTORAGE2DPROC glad_glTextureStorage2D = NULL;
PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC glad_glTextureStorage2DMultisample = NULL;
PFNGLTEXTURESTORAGE3DPROC glad_glTextureStorage3D = NULL;
PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC glad_glTextureStorage3DMultisample = NULL;
PFNGLTEXTURESUBIMAGE1DPROC glad_glTextureSubImage1D = NULL;
PFNGLTEXTURESUBIMAGE2DPROC glad_glTextureSubImage2D = NULL;
PFNGLTEXTURESUBIMAGE3DPROC glad_glTextureSubImage3D = NULL;
PFNGLTEXTUREVIEWPROC glad_glTextureView = NULL;
PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC glad_glTransformFeedbackBufferBase = NULL;
PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC glad_glTransformFeedbackBufferRange = NULL;
PFNGLTRANSFORMFEEDBACKVARYINGSPROC glad_glTransformFeedbackVaryings = NULL;
PFNGLUNIFORM1DPROC glad_glUniform1d = NULL;
PFNGLUNIFORM1DVPROC glad_glUniform1dv = NULL;
@ -878,19 +757,10 @@ PFNGLUNIFORMMATRIX4X3DVPROC glad_glUniformMatrix4x3dv = NULL;
PFNGLUNIFORMMATRIX4X3FVPROC glad_glUniformMatrix4x3fv = NULL;
PFNGLUNIFORMSUBROUTINESUIVPROC glad_glUniformSubroutinesuiv = NULL;
PFNGLUNMAPBUFFERPROC glad_glUnmapBuffer = NULL;
PFNGLUNMAPNAMEDBUFFERPROC glad_glUnmapNamedBuffer = NULL;
PFNGLUSEPROGRAMPROC glad_glUseProgram = NULL;
PFNGLUSEPROGRAMSTAGESPROC glad_glUseProgramStages = NULL;
PFNGLVALIDATEPROGRAMPROC glad_glValidateProgram = NULL;
PFNGLVALIDATEPROGRAMPIPELINEPROC glad_glValidateProgramPipeline = NULL;
PFNGLVERTEXARRAYATTRIBBINDINGPROC glad_glVertexArrayAttribBinding = NULL;
PFNGLVERTEXARRAYATTRIBFORMATPROC glad_glVertexArrayAttribFormat = NULL;
PFNGLVERTEXARRAYATTRIBIFORMATPROC glad_glVertexArrayAttribIFormat = NULL;
PFNGLVERTEXARRAYATTRIBLFORMATPROC glad_glVertexArrayAttribLFormat = NULL;
PFNGLVERTEXARRAYBINDINGDIVISORPROC glad_glVertexArrayBindingDivisor = NULL;
PFNGLVERTEXARRAYELEMENTBUFFERPROC glad_glVertexArrayElementBuffer = NULL;
PFNGLVERTEXARRAYVERTEXBUFFERPROC glad_glVertexArrayVertexBuffer = NULL;
PFNGLVERTEXARRAYVERTEXBUFFERSPROC glad_glVertexArrayVertexBuffers = NULL;
PFNGLVERTEXATTRIB1DPROC glad_glVertexAttrib1d = NULL;
PFNGLVERTEXATTRIB1DVPROC glad_glVertexAttrib1dv = NULL;
PFNGLVERTEXATTRIB1FPROC glad_glVertexAttrib1f = NULL;
@ -986,8 +856,15 @@ PFNGLWAITSYNCPROC glad_glWaitSync = NULL;
int GLAD_GL_ARB_buffer_storage = 0;
int GLAD_GL_ARB_clear_texture = 0;
int GLAD_GL_ARB_get_texture_sub_image = 0;
int GLAD_GL_ARB_texture_compression_bptc = 0;
int GLAD_GL_EXT_buffer_storage = 0;
int GLAD_GL_EXT_clip_cull_distance = 0;
int GLAD_GL_EXT_texture_compression_s3tc = 0;
PFNGLBUFFERSTORAGEPROC glad_glBufferStorage = NULL;
PFNGLCLEARTEXIMAGEPROC glad_glClearTexImage = NULL;
PFNGLCLEARTEXSUBIMAGEPROC glad_glClearTexSubImage = NULL;
PFNGLGETTEXTURESUBIMAGEPROC glad_glGetTextureSubImage = NULL;
PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC glad_glGetCompressedTextureSubImage = NULL;
PFNGLBUFFERSTORAGEEXTPROC glad_glBufferStorageEXT = NULL;
static void load_GL_VERSION_1_0(GLADloadproc load) {
if(!GLAD_GL_VERSION_1_0) return;
@ -1605,150 +1482,6 @@ static void load_GL_VERSION_4_3(GLADloadproc load) {
glad_glGetObjectPtrLabel = (PFNGLGETOBJECTPTRLABELPROC)load("glGetObjectPtrLabel");
glad_glGetPointerv = (PFNGLGETPOINTERVPROC)load("glGetPointerv");
}
static void load_GL_VERSION_4_4(GLADloadproc load) {
if(!GLAD_GL_VERSION_4_4) return;
glad_glBufferStorage = (PFNGLBUFFERSTORAGEPROC)load("glBufferStorage");
glad_glClearTexImage = (PFNGLCLEARTEXIMAGEPROC)load("glClearTexImage");
glad_glClearTexSubImage = (PFNGLCLEARTEXSUBIMAGEPROC)load("glClearTexSubImage");
glad_glBindBuffersBase = (PFNGLBINDBUFFERSBASEPROC)load("glBindBuffersBase");
glad_glBindBuffersRange = (PFNGLBINDBUFFERSRANGEPROC)load("glBindBuffersRange");
glad_glBindTextures = (PFNGLBINDTEXTURESPROC)load("glBindTextures");
glad_glBindSamplers = (PFNGLBINDSAMPLERSPROC)load("glBindSamplers");
glad_glBindImageTextures = (PFNGLBINDIMAGETEXTURESPROC)load("glBindImageTextures");
glad_glBindVertexBuffers = (PFNGLBINDVERTEXBUFFERSPROC)load("glBindVertexBuffers");
}
static void load_GL_VERSION_4_5(GLADloadproc load) {
if(!GLAD_GL_VERSION_4_5) return;
glad_glClipControl = (PFNGLCLIPCONTROLPROC)load("glClipControl");
glad_glCreateTransformFeedbacks = (PFNGLCREATETRANSFORMFEEDBACKSPROC)load("glCreateTransformFeedbacks");
glad_glTransformFeedbackBufferBase = (PFNGLTRANSFORMFEEDBACKBUFFERBASEPROC)load("glTransformFeedbackBufferBase");
glad_glTransformFeedbackBufferRange = (PFNGLTRANSFORMFEEDBACKBUFFERRANGEPROC)load("glTransformFeedbackBufferRange");
glad_glGetTransformFeedbackiv = (PFNGLGETTRANSFORMFEEDBACKIVPROC)load("glGetTransformFeedbackiv");
glad_glGetTransformFeedbacki_v = (PFNGLGETTRANSFORMFEEDBACKI_VPROC)load("glGetTransformFeedbacki_v");
glad_glGetTransformFeedbacki64_v = (PFNGLGETTRANSFORMFEEDBACKI64_VPROC)load("glGetTransformFeedbacki64_v");
glad_glCreateBuffers = (PFNGLCREATEBUFFERSPROC)load("glCreateBuffers");
glad_glNamedBufferStorage = (PFNGLNAMEDBUFFERSTORAGEPROC)load("glNamedBufferStorage");
glad_glNamedBufferData = (PFNGLNAMEDBUFFERDATAPROC)load("glNamedBufferData");
glad_glNamedBufferSubData = (PFNGLNAMEDBUFFERSUBDATAPROC)load("glNamedBufferSubData");
glad_glCopyNamedBufferSubData = (PFNGLCOPYNAMEDBUFFERSUBDATAPROC)load("glCopyNamedBufferSubData");
glad_glClearNamedBufferData = (PFNGLCLEARNAMEDBUFFERDATAPROC)load("glClearNamedBufferData");
glad_glClearNamedBufferSubData = (PFNGLCLEARNAMEDBUFFERSUBDATAPROC)load("glClearNamedBufferSubData");
glad_glMapNamedBuffer = (PFNGLMAPNAMEDBUFFERPROC)load("glMapNamedBuffer");
glad_glMapNamedBufferRange = (PFNGLMAPNAMEDBUFFERRANGEPROC)load("glMapNamedBufferRange");
glad_glUnmapNamedBuffer = (PFNGLUNMAPNAMEDBUFFERPROC)load("glUnmapNamedBuffer");
glad_glFlushMappedNamedBufferRange = (PFNGLFLUSHMAPPEDNAMEDBUFFERRANGEPROC)load("glFlushMappedNamedBufferRange");
glad_glGetNamedBufferParameteriv = (PFNGLGETNAMEDBUFFERPARAMETERIVPROC)load("glGetNamedBufferParameteriv");
glad_glGetNamedBufferParameteri64v = (PFNGLGETNAMEDBUFFERPARAMETERI64VPROC)load("glGetNamedBufferParameteri64v");
glad_glGetNamedBufferPointerv = (PFNGLGETNAMEDBUFFERPOINTERVPROC)load("glGetNamedBufferPointerv");
glad_glGetNamedBufferSubData = (PFNGLGETNAMEDBUFFERSUBDATAPROC)load("glGetNamedBufferSubData");
glad_glCreateFramebuffers = (PFNGLCREATEFRAMEBUFFERSPROC)load("glCreateFramebuffers");
glad_glNamedFramebufferRenderbuffer = (PFNGLNAMEDFRAMEBUFFERRENDERBUFFERPROC)load("glNamedFramebufferRenderbuffer");
glad_glNamedFramebufferParameteri = (PFNGLNAMEDFRAMEBUFFERPARAMETERIPROC)load("glNamedFramebufferParameteri");
glad_glNamedFramebufferTexture = (PFNGLNAMEDFRAMEBUFFERTEXTUREPROC)load("glNamedFramebufferTexture");
glad_glNamedFramebufferTextureLayer = (PFNGLNAMEDFRAMEBUFFERTEXTURELAYERPROC)load("glNamedFramebufferTextureLayer");
glad_glNamedFramebufferDrawBuffer = (PFNGLNAMEDFRAMEBUFFERDRAWBUFFERPROC)load("glNamedFramebufferDrawBuffer");
glad_glNamedFramebufferDrawBuffers = (PFNGLNAMEDFRAMEBUFFERDRAWBUFFERSPROC)load("glNamedFramebufferDrawBuffers");
glad_glNamedFramebufferReadBuffer = (PFNGLNAMEDFRAMEBUFFERREADBUFFERPROC)load("glNamedFramebufferReadBuffer");
glad_glInvalidateNamedFramebufferData = (PFNGLINVALIDATENAMEDFRAMEBUFFERDATAPROC)load("glInvalidateNamedFramebufferData");
glad_glInvalidateNamedFramebufferSubData = (PFNGLINVALIDATENAMEDFRAMEBUFFERSUBDATAPROC)load("glInvalidateNamedFramebufferSubData");
glad_glClearNamedFramebufferiv = (PFNGLCLEARNAMEDFRAMEBUFFERIVPROC)load("glClearNamedFramebufferiv");
glad_glClearNamedFramebufferuiv = (PFNGLCLEARNAMEDFRAMEBUFFERUIVPROC)load("glClearNamedFramebufferuiv");
glad_glClearNamedFramebufferfv = (PFNGLCLEARNAMEDFRAMEBUFFERFVPROC)load("glClearNamedFramebufferfv");
glad_glClearNamedFramebufferfi = (PFNGLCLEARNAMEDFRAMEBUFFERFIPROC)load("glClearNamedFramebufferfi");
glad_glBlitNamedFramebuffer = (PFNGLBLITNAMEDFRAMEBUFFERPROC)load("glBlitNamedFramebuffer");
glad_glCheckNamedFramebufferStatus = (PFNGLCHECKNAMEDFRAMEBUFFERSTATUSPROC)load("glCheckNamedFramebufferStatus");
glad_glGetNamedFramebufferParameteriv = (PFNGLGETNAMEDFRAMEBUFFERPARAMETERIVPROC)load("glGetNamedFramebufferParameteriv");
glad_glGetNamedFramebufferAttachmentParameteriv = (PFNGLGETNAMEDFRAMEBUFFERATTACHMENTPARAMETERIVPROC)load("glGetNamedFramebufferAttachmentParameteriv");
glad_glCreateRenderbuffers = (PFNGLCREATERENDERBUFFERSPROC)load("glCreateRenderbuffers");
glad_glNamedRenderbufferStorage = (PFNGLNAMEDRENDERBUFFERSTORAGEPROC)load("glNamedRenderbufferStorage");
glad_glNamedRenderbufferStorageMultisample = (PFNGLNAMEDRENDERBUFFERSTORAGEMULTISAMPLEPROC)load("glNamedRenderbufferStorageMultisample");
glad_glGetNamedRenderbufferParameteriv = (PFNGLGETNAMEDRENDERBUFFERPARAMETERIVPROC)load("glGetNamedRenderbufferParameteriv");
glad_glCreateTextures = (PFNGLCREATETEXTURESPROC)load("glCreateTextures");
glad_glTextureBuffer = (PFNGLTEXTUREBUFFERPROC)load("glTextureBuffer");
glad_glTextureBufferRange = (PFNGLTEXTUREBUFFERRANGEPROC)load("glTextureBufferRange");
glad_glTextureStorage1D = (PFNGLTEXTURESTORAGE1DPROC)load("glTextureStorage1D");
glad_glTextureStorage2D = (PFNGLTEXTURESTORAGE2DPROC)load("glTextureStorage2D");
glad_glTextureStorage3D = (PFNGLTEXTURESTORAGE3DPROC)load("glTextureStorage3D");
glad_glTextureStorage2DMultisample = (PFNGLTEXTURESTORAGE2DMULTISAMPLEPROC)load("glTextureStorage2DMultisample");
glad_glTextureStorage3DMultisample = (PFNGLTEXTURESTORAGE3DMULTISAMPLEPROC)load("glTextureStorage3DMultisample");
glad_glTextureSubImage1D = (PFNGLTEXTURESUBIMAGE1DPROC)load("glTextureSubImage1D");
glad_glTextureSubImage2D = (PFNGLTEXTURESUBIMAGE2DPROC)load("glTextureSubImage2D");
glad_glTextureSubImage3D = (PFNGLTEXTURESUBIMAGE3DPROC)load("glTextureSubImage3D");
glad_glCompressedTextureSubImage1D = (PFNGLCOMPRESSEDTEXTURESUBIMAGE1DPROC)load("glCompressedTextureSubImage1D");
glad_glCompressedTextureSubImage2D = (PFNGLCOMPRESSEDTEXTURESUBIMAGE2DPROC)load("glCompressedTextureSubImage2D");
glad_glCompressedTextureSubImage3D = (PFNGLCOMPRESSEDTEXTURESUBIMAGE3DPROC)load("glCompressedTextureSubImage3D");
glad_glCopyTextureSubImage1D = (PFNGLCOPYTEXTURESUBIMAGE1DPROC)load("glCopyTextureSubImage1D");
glad_glCopyTextureSubImage2D = (PFNGLCOPYTEXTURESUBIMAGE2DPROC)load("glCopyTextureSubImage2D");
glad_glCopyTextureSubImage3D = (PFNGLCOPYTEXTURESUBIMAGE3DPROC)load("glCopyTextureSubImage3D");
glad_glTextureParameterf = (PFNGLTEXTUREPARAMETERFPROC)load("glTextureParameterf");
glad_glTextureParameterfv = (PFNGLTEXTUREPARAMETERFVPROC)load("glTextureParameterfv");
glad_glTextureParameteri = (PFNGLTEXTUREPARAMETERIPROC)load("glTextureParameteri");
glad_glTextureParameterIiv = (PFNGLTEXTUREPARAMETERIIVPROC)load("glTextureParameterIiv");
glad_glTextureParameterIuiv = (PFNGLTEXTUREPARAMETERIUIVPROC)load("glTextureParameterIuiv");
glad_glTextureParameteriv = (PFNGLTEXTUREPARAMETERIVPROC)load("glTextureParameteriv");
glad_glGenerateTextureMipmap = (PFNGLGENERATETEXTUREMIPMAPPROC)load("glGenerateTextureMipmap");
glad_glBindTextureUnit = (PFNGLBINDTEXTUREUNITPROC)load("glBindTextureUnit");
glad_glGetTextureImage = (PFNGLGETTEXTUREIMAGEPROC)load("glGetTextureImage");
glad_glGetCompressedTextureImage = (PFNGLGETCOMPRESSEDTEXTUREIMAGEPROC)load("glGetCompressedTextureImage");
glad_glGetTextureLevelParameterfv = (PFNGLGETTEXTURELEVELPARAMETERFVPROC)load("glGetTextureLevelParameterfv");
glad_glGetTextureLevelParameteriv = (PFNGLGETTEXTURELEVELPARAMETERIVPROC)load("glGetTextureLevelParameteriv");
glad_glGetTextureParameterfv = (PFNGLGETTEXTUREPARAMETERFVPROC)load("glGetTextureParameterfv");
glad_glGetTextureParameterIiv = (PFNGLGETTEXTUREPARAMETERIIVPROC)load("glGetTextureParameterIiv");
glad_glGetTextureParameterIuiv = (PFNGLGETTEXTUREPARAMETERIUIVPROC)load("glGetTextureParameterIuiv");
glad_glGetTextureParameteriv = (PFNGLGETTEXTUREPARAMETERIVPROC)load("glGetTextureParameteriv");
glad_glCreateVertexArrays = (PFNGLCREATEVERTEXARRAYSPROC)load("glCreateVertexArrays");
glad_glDisableVertexArrayAttrib = (PFNGLDISABLEVERTEXARRAYATTRIBPROC)load("glDisableVertexArrayAttrib");
glad_glEnableVertexArrayAttrib = (PFNGLENABLEVERTEXARRAYATTRIBPROC)load("glEnableVertexArrayAttrib");
glad_glVertexArrayElementBuffer = (PFNGLVERTEXARRAYELEMENTBUFFERPROC)load("glVertexArrayElementBuffer");
glad_glVertexArrayVertexBuffer = (PFNGLVERTEXARRAYVERTEXBUFFERPROC)load("glVertexArrayVertexBuffer");
glad_glVertexArrayVertexBuffers = (PFNGLVERTEXARRAYVERTEXBUFFERSPROC)load("glVertexArrayVertexBuffers");
glad_glVertexArrayAttribBinding = (PFNGLVERTEXARRAYATTRIBBINDINGPROC)load("glVertexArrayAttribBinding");
glad_glVertexArrayAttribFormat = (PFNGLVERTEXARRAYATTRIBFORMATPROC)load("glVertexArrayAttribFormat");
glad_glVertexArrayAttribIFormat = (PFNGLVERTEXARRAYATTRIBIFORMATPROC)load("glVertexArrayAttribIFormat");
glad_glVertexArrayAttribLFormat = (PFNGLVERTEXARRAYATTRIBLFORMATPROC)load("glVertexArrayAttribLFormat");
glad_glVertexArrayBindingDivisor = (PFNGLVERTEXARRAYBINDINGDIVISORPROC)load("glVertexArrayBindingDivisor");
glad_glGetVertexArrayiv = (PFNGLGETVERTEXARRAYIVPROC)load("glGetVertexArrayiv");
glad_glGetVertexArrayIndexediv = (PFNGLGETVERTEXARRAYINDEXEDIVPROC)load("glGetVertexArrayIndexediv");
glad_glGetVertexArrayIndexed64iv = (PFNGLGETVERTEXARRAYINDEXED64IVPROC)load("glGetVertexArrayIndexed64iv");
glad_glCreateSamplers = (PFNGLCREATESAMPLERSPROC)load("glCreateSamplers");
glad_glCreateProgramPipelines = (PFNGLCREATEPROGRAMPIPELINESPROC)load("glCreateProgramPipelines");
glad_glCreateQueries = (PFNGLCREATEQUERIESPROC)load("glCreateQueries");
glad_glGetQueryBufferObjecti64v = (PFNGLGETQUERYBUFFEROBJECTI64VPROC)load("glGetQueryBufferObjecti64v");
glad_glGetQueryBufferObjectiv = (PFNGLGETQUERYBUFFEROBJECTIVPROC)load("glGetQueryBufferObjectiv");
glad_glGetQueryBufferObjectui64v = (PFNGLGETQUERYBUFFEROBJECTUI64VPROC)load("glGetQueryBufferObjectui64v");
glad_glGetQueryBufferObjectuiv = (PFNGLGETQUERYBUFFEROBJECTUIVPROC)load("glGetQueryBufferObjectuiv");
glad_glMemoryBarrierByRegion = (PFNGLMEMORYBARRIERBYREGIONPROC)load("glMemoryBarrierByRegion");
glad_glGetTextureSubImage = (PFNGLGETTEXTURESUBIMAGEPROC)load("glGetTextureSubImage");
glad_glGetCompressedTextureSubImage = (PFNGLGETCOMPRESSEDTEXTURESUBIMAGEPROC)load("glGetCompressedTextureSubImage");
glad_glGetGraphicsResetStatus = (PFNGLGETGRAPHICSRESETSTATUSPROC)load("glGetGraphicsResetStatus");
glad_glGetnCompressedTexImage = (PFNGLGETNCOMPRESSEDTEXIMAGEPROC)load("glGetnCompressedTexImage");
glad_glGetnTexImage = (PFNGLGETNTEXIMAGEPROC)load("glGetnTexImage");
glad_glGetnUniformdv = (PFNGLGETNUNIFORMDVPROC)load("glGetnUniformdv");
glad_glGetnUniformfv = (PFNGLGETNUNIFORMFVPROC)load("glGetnUniformfv");
glad_glGetnUniformiv = (PFNGLGETNUNIFORMIVPROC)load("glGetnUniformiv");
glad_glGetnUniformuiv = (PFNGLGETNUNIFORMUIVPROC)load("glGetnUniformuiv");
glad_glReadnPixels = (PFNGLREADNPIXELSPROC)load("glReadnPixels");
glad_glGetnMapdv = (PFNGLGETNMAPDVPROC)load("glGetnMapdv");
glad_glGetnMapfv = (PFNGLGETNMAPFVPROC)load("glGetnMapfv");
glad_glGetnMapiv = (PFNGLGETNMAPIVPROC)load("glGetnMapiv");
glad_glGetnPixelMapfv = (PFNGLGETNPIXELMAPFVPROC)load("glGetnPixelMapfv");
glad_glGetnPixelMapuiv = (PFNGLGETNPIXELMAPUIVPROC)load("glGetnPixelMapuiv");
glad_glGetnPixelMapusv = (PFNGLGETNPIXELMAPUSVPROC)load("glGetnPixelMapusv");
glad_glGetnPolygonStipple = (PFNGLGETNPOLYGONSTIPPLEPROC)load("glGetnPolygonStipple");
glad_glGetnColorTable = (PFNGLGETNCOLORTABLEPROC)load("glGetnColorTable");
glad_glGetnConvolutionFilter = (PFNGLGETNCONVOLUTIONFILTERPROC)load("glGetnConvolutionFilter");
glad_glGetnSeparableFilter = (PFNGLGETNSEPARABLEFILTERPROC)load("glGetnSeparableFilter");
glad_glGetnHistogram = (PFNGLGETNHISTOGRAMPROC)load("glGetnHistogram");
glad_glGetnMinmax = (PFNGLGETNMINMAXPROC)load("glGetnMinmax");
glad_glTextureBarrier = (PFNGLTEXTUREBARRIERPROC)load("glTextureBarrier");
}
static void load_GL_VERSION_4_6(GLADloadproc load) {
if(!GLAD_GL_VERSION_4_6) return;
glad_glSpecializeShader = (PFNGLSPECIALIZESHADERPROC)load("glSpecializeShader");
glad_glMultiDrawArraysIndirectCount = (PFNGLMULTIDRAWARRAYSINDIRECTCOUNTPROC)load("glMultiDrawArraysIndirectCount");
glad_glMultiDrawElementsIndirectCount = (PFNGLMULTIDRAWELEMENTSINDIRECTCOUNTPROC)load("glMultiDrawElementsIndirectCount");
glad_glPolygonOffsetClamp = (PFNGLPOLYGONOFFSETCLAMPPROC)load("glPolygonOffsetClamp");
}
static void load_GL_ARB_buffer_storage(GLADloadproc load) {
if(!GLAD_GL_ARB_buffer_storage) return;
glad_glBufferStorage = (PFNGLBUFFERSTORAGEPROC)load("glBufferStorage");
@ -1768,6 +1501,8 @@ static int find_extensionsGL(void) {
GLAD_GL_ARB_buffer_storage = has_ext("GL_ARB_buffer_storage");
GLAD_GL_ARB_clear_texture = has_ext("GL_ARB_clear_texture");
GLAD_GL_ARB_get_texture_sub_image = has_ext("GL_ARB_get_texture_sub_image");
GLAD_GL_ARB_texture_compression_bptc = has_ext("GL_ARB_texture_compression_bptc");
GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc");
free_exts();
return 1;
}
@ -1824,12 +1559,9 @@ static void find_coreGL(void) {
GLAD_GL_VERSION_4_1 = (major == 4 && minor >= 1) || major > 4;
GLAD_GL_VERSION_4_2 = (major == 4 && minor >= 2) || major > 4;
GLAD_GL_VERSION_4_3 = (major == 4 && minor >= 3) || major > 4;
GLAD_GL_VERSION_4_4 = (major == 4 && minor >= 4) || major > 4;
GLAD_GL_VERSION_4_5 = (major == 4 && minor >= 5) || major > 4;
GLAD_GL_VERSION_4_6 = (major == 4 && minor >= 6) || major > 4;
if (GLVersion.major > 4 || (GLVersion.major >= 4 && GLVersion.minor >= 6)) {
if (GLVersion.major > 4 || (GLVersion.major >= 4 && GLVersion.minor >= 3)) {
max_loaded_major = 4;
max_loaded_minor = 6;
max_loaded_minor = 3;
}
}
@ -1855,9 +1587,6 @@ int gladLoadGLLoader(GLADloadproc load) {
load_GL_VERSION_4_1(load);
load_GL_VERSION_4_2(load);
load_GL_VERSION_4_3(load);
load_GL_VERSION_4_4(load);
load_GL_VERSION_4_5(load);
load_GL_VERSION_4_6(load);
if (!find_extensionsGL()) return 0;
load_GL_ARB_buffer_storage(load);
@ -2244,6 +1973,7 @@ static int find_extensionsGLES2(void) {
if (!get_exts()) return 0;
GLAD_GL_EXT_buffer_storage = has_ext("GL_EXT_buffer_storage");
GLAD_GL_EXT_clip_cull_distance = has_ext("GL_EXT_clip_cull_distance");
GLAD_GL_EXT_texture_compression_s3tc = has_ext("GL_EXT_texture_compression_s3tc");
free_exts();
return 1;
}

View file

@ -168,9 +168,8 @@ public final class SettingsFragmentPresenter {
sl.add(new PremiumSingleChoiceSetting(SettingsFile.KEY_DESIGN, Settings.SECTION_PREMIUM, R.string.design, 0, R.array.designNamesOld, R.array.designValuesOld, 0, design, mView));
}
String[] textureFilterNames = NativeLibrary.GetTextureFilterNames();
Setting textureFilterName = premiumSection.getSetting(SettingsFile.KEY_TEXTURE_FILTER_NAME);
sl.add(new StringSingleChoiceSetting(SettingsFile.KEY_TEXTURE_FILTER_NAME, Settings.SECTION_PREMIUM, R.string.texture_filter_name, R.string.texture_filter_description, textureFilterNames, textureFilterNames, "none", textureFilterName));
sl.add(new SingleChoiceSetting(SettingsFile.KEY_TEXTURE_FILTER_NAME, Settings.SECTION_PREMIUM, R.string.texture_filter_name, 0, R.array.textureFilterNames, R.array.textureFilterValues, 0, textureFilterName));
}
private void addGeneralSettings(ArrayList<SettingsItem> sl) {

View file

@ -140,7 +140,7 @@ void Config::ReadValues() {
ReadSetting("Core", Settings::values.cpu_clock_percentage);
// Premium
ReadSetting("Premium", Settings::values.texture_filter_name);
ReadSetting("Premium", Settings::values.texture_filter);
// Renderer
Settings::values.use_gles = sdl2_config->GetBoolean("Renderer", "use_gles", true);

View file

@ -44,7 +44,6 @@
#include "jni/native.h"
#include "jni/ndk_motion.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
#include "video_core/video_core.h"
namespace {
@ -630,17 +629,6 @@ void Java_org_citra_citra_1emu_NativeLibrary_Run__Ljava_lang_String_2(JNIEnv* en
}
}
jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(JNIEnv* env,
jclass clazz) {
auto names = OpenGL::TextureFilterer::GetFilterNames();
jobjectArray ret = (jobjectArray)env->NewObjectArray(static_cast<jsize>(names.size()),
env->FindClass("java/lang/String"),
env->NewStringUTF(""));
for (jsize i = 0; i < names.size(); ++i)
env->SetObjectArrayElement(ret, i, env->NewStringUTF(names[i].data()));
return ret;
}
void Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env, jclass clazz) {
if (g_ndk_factory) {
g_ndk_factory->ReloadCameraDevices();

View file

@ -138,9 +138,6 @@ JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetUserSetting
JNIEXPORT jdoubleArray JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetPerfStats(JNIEnv* env,
jclass clazz);
JNIEXPORT jobjectArray JNICALL
Java_org_citra_citra_1emu_NativeLibrary_GetTextureFilterNames(JNIEnv* env, jclass clazz);
JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_ReloadCameraDevices(JNIEnv* env,
jclass clazz);

View file

@ -179,4 +179,22 @@
<integer-array name="graphicsApiValues">
<item>1</item>
</integer-array>
<string-array name="textureFilterNames">
<item>None</item>
<item>Anime4K</item>
<item>Bicubic</item>
<item>Nearest Neighbor</item>
<item>ScaleForce</item>
<item>xBRZ</item>
</string-array>
<integer-array name="textureFilterValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</integer-array>
</resources>

View file

@ -148,7 +148,7 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.frame_limit);
ReadSetting("Renderer", Settings::values.use_vsync_new);
ReadSetting("Renderer", Settings::values.texture_filter_name);
ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.mono_render_option);
ReadSetting("Renderer", Settings::values.render_3d);

View file

@ -136,8 +136,9 @@ use_disk_shader_cache =
# factor for the 3DS resolution
resolution_factor =
# Texture filter name
texture_filter_name =
# Texture filter
# 0: None, 1: Anime4K, 2: Bicubic, 3: Nearest Neighbor, 4: ScaleForce, 5: xBRZ
texture_filter =
# Limits the speed of the game to run no faster than this value as a percentage of target speed.
# Will not have an effect if unthrottled is enabled.

View file

@ -644,7 +644,7 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
ReadGlobalSetting(Settings::values.texture_filter_name);
ReadGlobalSetting(Settings::values.texture_filter);
if (global) {
ReadBasicSetting(Settings::values.use_shader_jit);
@ -1122,7 +1122,7 @@ void Config::SaveRendererValues() {
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);
WriteGlobalSetting(Settings::values.texture_filter_name);
WriteGlobalSetting(Settings::values.texture_filter);
if (global) {
WriteSetting(QStringLiteral("use_shader_jit"), Settings::values.use_shader_jit.GetValue(),

View file

@ -33,16 +33,6 @@ void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
}
}
template <>
void ConfigurationShared::SetPerGameSetting(
QComboBox* combobox, const Settings::SwitchableSetting<std::string>* setting) {
const int index =
static_cast<int>(combobox->findText(QString::fromStdString(setting->GetValue())));
combobox->setCurrentIndex(setting->UsingGlobal()
? ConfigurationShared::USE_GLOBAL_INDEX
: index + ConfigurationShared::USE_GLOBAL_OFFSET);
}
void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
if (highlighted) {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")

View file

@ -78,11 +78,6 @@ void SetPerGameSetting(QComboBox* combobox,
ConfigurationShared::USE_GLOBAL_OFFSET);
}
/// Specialization for string settings
template <>
void SetPerGameSetting(QComboBox* combobox,
const Settings::SwitchableSetting<std::string>* setting);
/// Given an index of a combobox setting extracts the setting taking into
/// account per-game status
template <typename Type, bool ranged>

View file

@ -8,15 +8,11 @@
#include "common/settings.h"
#include "ui_configure_enhancements.h"
#include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureEnhancements>()) {
ui->setupUi(this);
for (const auto& filter : OpenGL::TextureFilterer::GetFilterNames())
ui->texture_filter_combobox->addItem(QString::fromStdString(filter.data()));
SetupPerGameUI();
SetConfiguration();
@ -60,9 +56,9 @@ void ConfigureEnhancements::SetConfiguration() {
ConfigurationShared::SetPerGameSetting(ui->resolution_factor_combobox,
&Settings::values.resolution_factor);
ConfigurationShared::SetPerGameSetting(ui->texture_filter_combobox,
&Settings::values.texture_filter_name);
&Settings::values.texture_filter);
ConfigurationShared::SetHighlight(ui->widget_texture_filter,
!Settings::values.texture_filter_name.UsingGlobal());
!Settings::values.texture_filter.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->layout_combobox,
&Settings::values.layout_option);
} else {
@ -70,13 +66,8 @@ void ConfigureEnhancements::SetConfiguration() {
Settings::values.resolution_factor.GetValue());
ui->layout_combobox->setCurrentIndex(
static_cast<int>(Settings::values.layout_option.GetValue()));
int tex_filter_idx = ui->texture_filter_combobox->findText(
QString::fromStdString(Settings::values.texture_filter_name.GetValue()));
if (tex_filter_idx == -1) {
ui->texture_filter_combobox->setCurrentIndex(0);
} else {
ui->texture_filter_combobox->setCurrentIndex(tex_filter_idx);
}
ui->texture_filter_combobox->setCurrentIndex(
static_cast<int>(Settings::values.texture_filter.GetValue()));
}
ui->render_3d_combobox->setCurrentIndex(
@ -155,9 +146,8 @@ void ConfigureEnhancements::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.filter_mode,
ui->toggle_linear_filter, linear_filter);
ConfigurationShared::ApplyPerGameSetting(
&Settings::values.texture_filter_name, ui->texture_filter_combobox,
[this](int index) { return ui->texture_filter_combobox->itemText(index).toStdString(); });
ConfigurationShared::ApplyPerGameSetting(&Settings::values.texture_filter,
ui->texture_filter_combobox);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.layout_option, ui->layout_combobox);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.swap_screen, ui->toggle_swap_screen,
swap_screen);
@ -179,7 +169,7 @@ void ConfigureEnhancements::SetupPerGameUI() {
// Block the global settings if a game is currently running that overrides them
if (Settings::IsConfiguringGlobal()) {
ui->widget_resolution->setEnabled(Settings::values.resolution_factor.UsingGlobal());
ui->widget_texture_filter->setEnabled(Settings::values.texture_filter_name.UsingGlobal());
ui->widget_texture_filter->setEnabled(Settings::values.texture_filter.UsingGlobal());
ui->toggle_linear_filter->setEnabled(Settings::values.filter_mode.UsingGlobal());
ui->toggle_swap_screen->setEnabled(Settings::values.swap_screen.UsingGlobal());
ui->toggle_upright_screen->setEnabled(Settings::values.upright_screen.UsingGlobal());

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>657</height>
<width>440</width>
<height>748</height>
</rect>
</property>
<property name="minimumSize">
@ -168,7 +168,38 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="texture_filter_combobox"/>
<widget class="QComboBox" name="texture_filter_combobox">
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Anime4K</string>
</property>
</item>
<item>
<property name="text">
<string>Bicubic</string>
</property>
</item>
<item>
<property name="text">
<string>Nearest Neighbor</string>
</property>
</item>
<item>
<property name="text">
<string>ScaleForce</string>
</property>
</item>
<item>
<property name="text">
<string>xBRZ</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
@ -352,7 +383,7 @@
</widget>
</item>
<item>
<widget class="QWidget" name="" native="true">
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="proportion_layout">
<property name="leftMargin">
<number>0</number>

View file

@ -52,6 +52,11 @@ namespace Common::Color {
return value >> 2;
}
/// Averages the RGB components of a color
[[nodiscard]] constexpr u8 AverageRgbComponents(const Common::Vec4<u8>& color) {
return (static_cast<u32>(color.r()) + color.g() + color.b()) / 3;
}
/**
* Decode a color stored in RGBA8 format
* @param bytes Pointer to encoded source color
@ -115,6 +120,44 @@ namespace Common::Color {
Convert4To8((pixel >> 4) & 0xF), Convert4To8(pixel & 0xF)};
}
/**
* Decode a color stored in IA8 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeIA8(const u8* bytes) {
return {bytes[1], bytes[1], bytes[1], bytes[0]};
}
/**
* Decode a color stored in I8 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeI8(const u8* bytes) {
return {bytes[0], bytes[0], bytes[0], 255};
}
/**
* Decode a color stored in A8 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeA8(const u8* bytes) {
return {0, 0, 0, bytes[0]};
}
/**
* Decode a color stored in IA4 format
* @param bytes Pointer to encoded source color
* @return Result color decoded as Common::Vec4<u8>
*/
[[nodiscard]] inline Common::Vec4<u8> DecodeIA4(const u8* bytes) {
u8 i = Common::Color::Convert4To8((bytes[0] & 0xF0) >> 4);
u8 a = Common::Color::Convert4To8(bytes[0] & 0x0F);
return {i, i, i, a};
}
/**
* Decode a depth value stored in D16 format
* @param bytes Pointer to encoded source value
@ -176,6 +219,7 @@ inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[1] = color.r();
bytes[0] = color.g();
}
/**
* Encode a color as RGB565 format
* @param color Source color to encode
@ -212,6 +256,43 @@ inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) {
std::memcpy(bytes, &data, sizeof(data));
}
/**
* Encode a color as IA8 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeIA8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[1] = AverageRgbComponents(color);
bytes[0] = color.a();
}
/**
* Encode a color as I8 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeI8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[0] = AverageRgbComponents(color);
}
/**
* Encode a color as A8 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeA8(const Common::Vec4<u8>& color, u8* bytes) {
bytes[0] = color.a();
}
/**
* Encode a color as IA4 format
* @param color Source color to encode
* @param bytes Destination pointer to store encoded color
*/
inline void EncodeIA4(const Common::Vec4<u8>& color, u8* bytes) {
bytes[0] = (Convert8To4(AverageRgbComponents(color)) << 4) | Convert8To4(color.a());
}
/**
* Encode a 16 bit depth value as D16 format
* @param value 16 bit source depth value to encode

View file

@ -41,6 +41,13 @@ inline u64 HashCombine(std::size_t& seed, const u64 hash) {
return seed ^= hash + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template <typename T>
struct IdentityHash {
std::size_t operator()(const T& value) const {
return value;
}
};
/// A helper template that ensures the padding in a struct is initialized by memsetting to 0.
template <typename T>
struct HashableStruct {

View file

@ -23,6 +23,22 @@ struct Rectangle {
constexpr Rectangle(T left, T top, T right, T bottom)
: left(left), top(top), right(right), bottom(bottom) {}
[[nodiscard]] constexpr bool operator==(const Rectangle<T>& rhs) const {
return (left == rhs.left) && (top == rhs.top) && (right == rhs.right) &&
(bottom == rhs.bottom);
}
[[nodiscard]] constexpr bool operator!=(const Rectangle<T>& rhs) const {
return !operator==(rhs);
}
[[nodiscard]] constexpr Rectangle<T> operator*(const T value) const {
return Rectangle{left * value, top * value, right * value, bottom * value};
}
[[nodiscard]] constexpr Rectangle<T> operator/(const T value) const {
return Rectangle{left / value, top / value, right / value, bottom / value};
}
[[nodiscard]] T GetWidth() const {
return std::abs(static_cast<std::make_signed_t<T>>(right - left));
}

View file

@ -5,6 +5,7 @@
#pragma once
#include <memory>
#include <span>
#include <vector>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
@ -105,6 +106,14 @@ public:
return cptr;
}
std::span<u8> GetWriteBytes(std::size_t size) {
return std::span{cptr, std::min(size, csize)};
}
std::span<const u8> GetReadBytes(std::size_t size) const {
return std::span{cptr, std::min(size, csize)};
}
std::size_t GetSize() const {
return csize;
}

View file

@ -42,6 +42,23 @@ std::string_view GetGraphicsAPIName(GraphicsAPI api) {
}
}
std::string_view GetTextureFilterName(TextureFilter filter) {
switch (filter) {
case TextureFilter::None:
return "None";
case TextureFilter::Anime4K:
return "Anime4K";
case TextureFilter::Bicubic:
return "Bicubic";
case TextureFilter::NearestNeighbor:
return "NearestNeighbor";
case TextureFilter::ScaleForce:
return "ScaleForce";
case TextureFilter::xBRZ:
return "xBRZ";
}
}
} // Anonymous namespace
Values values = {};
@ -126,7 +143,7 @@ void LogSettings() {
log_setting("Renderer_VSyncNew", values.use_vsync_new.GetValue());
log_setting("Renderer_PostProcessingShader", values.pp_shader_name.GetValue());
log_setting("Renderer_FilterMode", values.filter_mode.GetValue());
log_setting("Renderer_TextureFilterName", values.texture_filter_name.GetValue());
log_setting("Renderer_TextureFilter", GetTextureFilterName(values.texture_filter.GetValue()));
log_setting("Stereoscopy_Render3d", values.render_3d.GetValue());
log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue());
log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue());
@ -209,7 +226,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.use_vsync_new.SetGlobal(true);
values.resolution_factor.SetGlobal(true);
values.frame_limit.SetGlobal(true);
values.texture_filter_name.SetGlobal(true);
values.texture_filter.SetGlobal(true);
values.layout_option.SetGlobal(true);
values.swap_screen.SetGlobal(true);
values.upright_screen.SetGlobal(true);

View file

@ -70,6 +70,15 @@ enum class AudioEmulation : u32 {
LLEMultithreaded = 2,
};
enum class TextureFilter : u32 {
None = 0,
Anime4K = 1,
Bicubic = 2,
NearestNeighbor = 3,
ScaleForce = 4,
xBRZ = 5,
};
namespace NativeButton {
enum Values {
@ -431,7 +440,7 @@ struct Values {
Setting<bool> use_shader_jit{true, "use_shader_jit"};
SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"};
SwitchableSetting<u16, true> frame_limit{100, 0, 1000, "frame_limit"};
SwitchableSetting<std::string> texture_filter_name{"none", "texture_filter_name"};
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"};
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
SwitchableSetting<bool> swap_screen{false, "swap_screen"};

View file

@ -1,3 +1,5 @@
add_subdirectory(host_shaders)
add_library(video_core STATIC
command_processor.cpp
command_processor.h
@ -26,23 +28,27 @@ 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/framebuffer_base.cpp
rasterizer_cache/framebuffer_base.h
rasterizer_cache/pixel_format.cpp
rasterizer_cache/pixel_format.h
rasterizer_cache/rasterizer_cache.cpp
rasterizer_cache/rasterizer_cache.h
rasterizer_cache/rasterizer_cache_types.h
rasterizer_cache/rasterizer_cache_utils.cpp
rasterizer_cache/rasterizer_cache_utils.h
rasterizer_cache/surface_base.cpp
rasterizer_cache/surface_base.h
rasterizer_cache/surface_params.cpp
rasterizer_cache/surface_params.h
rasterizer_cache/texture_runtime.cpp
rasterizer_cache/texture_runtime.h
rasterizer_cache/texture_codec.h
rasterizer_cache/utils.cpp
rasterizer_cache/utils.h
renderer_opengl/frame_dumper_opengl.cpp
renderer_opengl/frame_dumper_opengl.h
renderer_opengl/gl_blit_helper.cpp
renderer_opengl/gl_blit_helper.h
renderer_opengl/gl_driver.cpp
renderer_opengl/gl_driver.h
renderer_opengl/gl_format_reinterpreter.cpp
renderer_opengl/gl_format_reinterpreter.h
renderer_opengl/gl_rasterizer.cpp
renderer_opengl/gl_rasterizer.h
renderer_opengl/gl_resource_manager.cpp
@ -61,6 +67,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
@ -68,24 +76,6 @@ add_library(video_core STATIC
renderer_opengl/post_processing_opengl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
renderer_opengl/texture_downloader_es.cpp
renderer_opengl/texture_downloader_es.h
renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.cpp
renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h
renderer_opengl/texture_filters/bicubic/bicubic.cpp
renderer_opengl/texture_filters/bicubic/bicubic.h
renderer_opengl/texture_filters/nearest_neighbor/nearest_neighbor.cpp
renderer_opengl/texture_filters/nearest_neighbor/nearest_neighbor.h
renderer_opengl/texture_filters/scale_force/scale_force.cpp
renderer_opengl/texture_filters/scale_force/scale_force.h
renderer_opengl/texture_filters/texture_filter_base.h
renderer_opengl/texture_filters/texture_filterer.cpp
renderer_opengl/texture_filters/texture_filterer.h
renderer_opengl/texture_filters/xbrz/xbrz_freescale.cpp
renderer_opengl/texture_filters/xbrz/xbrz_freescale.h
#temporary, move these back in alphabetical order before merging
renderer_opengl/gl_format_reinterpreter.cpp
renderer_opengl/gl_format_reinterpreter.h
renderer_software/rasterizer.cpp
renderer_software/rasterizer.h
renderer_software/renderer_software.cpp
@ -124,37 +114,8 @@ add_library(video_core STATIC
video_core.h
)
set(SHADER_FILES
renderer_opengl/depth_to_color.frag
renderer_opengl/depth_to_color.vert
renderer_opengl/ds_to_color.frag
renderer_opengl/texture_filters/anime4k/refine.frag
renderer_opengl/texture_filters/anime4k/x_gradient.frag
renderer_opengl/texture_filters/anime4k/y_gradient.frag
renderer_opengl/texture_filters/bicubic/bicubic.frag
renderer_opengl/texture_filters/nearest_neighbor/nearest_neighbor.frag
renderer_opengl/texture_filters/scale_force/scale_force.frag
renderer_opengl/texture_filters/tex_coord.vert
renderer_opengl/texture_filters/xbrz/xbrz_freescale.frag
renderer_opengl/texture_filters/xbrz/xbrz_freescale.vert
)
include(${CMAKE_CURRENT_SOURCE_DIR}/generate_shaders.cmake)
foreach(shader_file ${SHADER_FILES})
get_filename_component(shader_file_name ${shader_file} NAME)
GetShaderHeaderFile(${shader_file_name})
list(APPEND SHADER_HEADERS ${shader_header_file})
endforeach()
add_custom_target(shaders
BYPRODUCTS ${SHADER_HEADERS}
COMMAND "${CMAKE_COMMAND}" -P ${CMAKE_CURRENT_SOURCE_DIR}/generate_shaders.cmake
SOURCES ${SHADER_FILES}
)
add_dependencies(video_core shaders)
target_include_directories(video_core PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(video_core host_shaders)
target_include_directories(video_core PRIVATE ${HOST_SHADERS_INCLUDE})
create_target_directory_groups(video_core)

View file

@ -1,16 +0,0 @@
function(GetShaderHeaderFile shader_file_name)
set(shader_header_file ${CMAKE_CURRENT_BINARY_DIR}/shaders/${shader_file_name} PARENT_SCOPE)
endfunction()
foreach(shader_file ${SHADER_FILES})
file(READ ${shader_file} shader)
get_filename_component(shader_file_name ${shader_file} NAME)
string(REPLACE . _ shader_name ${shader_file_name})
GetShaderHeaderFile(${shader_file_name})
file(WRITE ${shader_header_file}
"#pragma once\n"
"constexpr std::string_view ${shader_name} = R\"(\n"
"${shader}"
")\";\n"
)
endforeach()

View file

@ -0,0 +1,60 @@
# Copyright 2023 Citra Emulator Project
# Licensed under GPLv2 or any later version
# Refer to the license.txt file included.
set(SHADER_FILES
format_reinterpreter/d24s8_to_rgba8.frag
format_reinterpreter/fullscreen_quad.vert
format_reinterpreter/rgba4_to_rgb5a1.frag
texture_filtering/bicubic.frag
texture_filtering/nearest_neighbor.frag
texture_filtering/refine.frag
texture_filtering/scale_force.frag
texture_filtering/tex_coord.vert
texture_filtering/xbrz_freescale.frag
texture_filtering/x_gradient.frag
texture_filtering/y_gradient.frag
full_screen_triangle.vert
opengl_present.frag
opengl_present.vert
opengl_present_anaglyph.frag
opengl_present_interlaced.frag
)
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
foreach(FILENAME IN ITEMS ${SHADER_FILES})
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
# Skip generating source headers on Vulkan exclusive files
if (NOT ${FILENAME} MATCHES "vulkan.*")
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
add_custom_command(
OUTPUT
${SOURCE_HEADER_FILE}
COMMAND
${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
MAIN_DEPENDENCY
${SOURCE_FILE}
DEPENDS
${INPUT_FILE}
# HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
)
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
endif()
endforeach()
set(SHADER_SOURCES ${SHADER_FILES})
list(APPEND SHADER_SOURCES ${GLSL_INCLUDES})
add_custom_target(host_shaders
DEPENDS
${SHADER_HEADERS}
SOURCES
${SHADER_SOURCES}
)

View file

@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2020 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
set(SOURCE_FILE ${CMAKE_ARGV3})
set(HEADER_FILE ${CMAKE_ARGV4})
set(INPUT_FILE ${CMAKE_ARGV5})
get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME)
string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME})
string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
FILE(READ ${SOURCE_FILE} line_contents)
# Replace double quotes with single quotes,
# as double quotes will be used to wrap the lines
STRING(REGEX REPLACE "\"" "'" line_contents "${line_contents}")
# CMake separates list elements with semicolons, but semicolons
# are used extensively in the shader code.
# Replace with a temporary marker, to be reverted later.
STRING(REGEX REPLACE ";" "{{SEMICOLON}}" line_contents "${line_contents}")
# Make every line an individual element in the CMake list.
STRING(REGEX REPLACE "\n" ";" line_contents "${line_contents}")
# Build the shader string, wrapping each line in double quotes.
foreach(line IN LISTS line_contents)
string(CONCAT CONTENTS "${CONTENTS}" \"${line}\\n\"\n)
endforeach()
# Revert the original semicolons in the source.
STRING(REGEX REPLACE "{{SEMICOLON}}" ";" CONTENTS "${CONTENTS}")
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
make_directory(${OUTPUT_DIR})
configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)

View file

@ -0,0 +1,33 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) in mediump vec2 dst_coord;
layout(location = 0) out lowp vec4 frag_color;
layout(binding = 0) uniform highp sampler2D depth;
layout(binding = 1) uniform lowp usampler2D stencil;
uniform mediump ivec2 dst_size;
uniform mediump ivec2 src_size;
uniform mediump ivec2 src_offset;
void main() {
mediump ivec2 tex_coord;
if (src_size == dst_size) {
tex_coord = ivec2(dst_coord);
} else {
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
mediump int y = tex_index / src_size.x;
tex_coord = ivec2(tex_index - y * src_size.x, y);
}
tex_coord -= src_offset;
highp uint depth_val =
uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0));
lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x;
highp uvec4 components =
uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu);
frag_color = vec4(components) / (exp2(8.0) - 1.0);
}

View file

@ -0,0 +1,17 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) out vec2 dst_coord;
uniform mediump ivec2 dst_size;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size);
}

View file

@ -0,0 +1,30 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) in mediump vec2 dst_coord;
layout(location = 0) out lowp vec4 frag_color;
layout(binding = 0) uniform lowp sampler2D source;
uniform mediump ivec2 dst_size;
uniform mediump ivec2 src_size;
uniform mediump ivec2 src_offset;
void main() {
mediump ivec2 tex_coord;
if (src_size == dst_size) {
tex_coord = ivec2(dst_coord);
} else {
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
mediump int y = tex_index / src_size.x;
tex_coord = ivec2(tex_index - y * src_size.x, y);
}
tex_coord -= src_offset;
lowp ivec4 rgba4 = ivec4(texelFetch(source, tex_coord, 0) * (exp2(4.0) - 1.0));
lowp ivec3 rgb5 =
((rgba4.rgb << ivec3(1, 2, 3)) | (rgba4.gba >> ivec3(3, 2, 1))) & 0x1F;
frag_color = vec4(vec3(rgb5) / (exp2(5.0) - 1.0), rgba4.a & 0x01);
}

View file

@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
//? #version 450
out gl_PerVertex {
vec4 gl_Position;
};
layout(location = 0) out vec2 texcoord;
layout (location = 0) uniform vec2 tex_scale;
layout (location = 1) uniform vec2 tex_offset;
void main() {
float x = float((gl_VertexID & 1) << 2);
float y = float((gl_VertexID & 2) << 1);
gl_Position = vec4(x - 1.0, y - 1.0, 0.0, 1.0);
texcoord = fma(vec2(x, y) / 2.0, tex_scale, tex_offset);
}

View file

@ -0,0 +1,18 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) in vec2 frag_tex_coord;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D color_texture;
uniform vec4 i_resolution;
uniform vec4 o_resolution;
uniform int layer;
void main() {
color = texture(color_texture, frag_tex_coord);
}

View file

@ -0,0 +1,23 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) in vec2 vert_position;
layout(location = 1) in vec2 vert_tex_coord;
layout(location = 0) out vec2 frag_tex_coord;
// This is a truncated 3x3 matrix for 2D transformations:
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
// The third column performs translation.
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
// implicitly be [0, 0, 1]
uniform mat3x2 modelview_matrix;
void main() {
// Multiply input position by the rotscale part of the matrix and then manually translate by
// the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
// to `vec3(vert_position.xy, 1.0)`
gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
frag_tex_coord = vert_tex_coord;
}

View file

@ -0,0 +1,32 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
// Anaglyph Red-Cyan shader based on Dubois algorithm
// Constants taken from the paper:
// "Conversion of a Stereo Pair to Anaglyph with
// the Least-Squares Projection Method"
// Eric Dubois, March 2009
const mat3 l = mat3( 0.437, 0.449, 0.164,
-0.062,-0.062,-0.024,
-0.048,-0.050,-0.017);
const mat3 r = mat3(-0.011,-0.032,-0.007,
0.377, 0.761, 0.009,
-0.026,-0.093, 1.234);
layout(location = 0) in vec2 frag_tex_coord;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D color_texture;
layout(binding = 1) uniform sampler2D color_texture_r;
uniform vec4 resolution;
uniform int layer;
void main() {
vec4 color_tex_l = texture(color_texture, frag_tex_coord);
vec4 color_tex_r = texture(color_texture_r, frag_tex_coord);
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
}

View file

@ -0,0 +1,22 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) in vec2 frag_tex_coord;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D color_texture;
layout(binding = 1) uniform sampler2D color_texture_r;
uniform vec4 o_resolution;
uniform int reverse_interlaced;
void main() {
float screen_row = o_resolution.x * frag_tex_coord.x;
if (int(screen_row) % 2 == reverse_interlaced)
color = texture(color_texture, frag_tex_coord);
else
color = texture(color_texture_r, frag_tex_coord);
}

View file

@ -0,0 +1,15 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string_view>
namespace HostShaders {
constexpr std::string_view @CONTENTS_NAME@ = {
@CONTENTS@
};
} // namespace HostShaders

View file

@ -1,11 +1,14 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 330
precision mediump float;
in vec2 tex_coord;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec4 frag_color;
out vec4 frag_color;
uniform sampler2D input_texture;
layout(binding = 0) uniform sampler2D input_texture;
// from http://www.java-gaming.org/index.php?topic=35123.0
vec4 cubic(float v) {
@ -18,9 +21,8 @@ vec4 cubic(float v) {
return vec4(x, y, z, w) * (1.0 / 6.0);
}
vec4 textureBicubic(sampler2D sampler, vec2 texCoords) {
vec2 texSize = vec2(textureSize(sampler, 0));
vec4 textureBicubic(sampler2D tex_sampler, vec2 texCoords) {
vec2 texSize = vec2(textureSize(tex_sampler, 0));
vec2 invTexSize = 1.0 / texSize;
texCoords = texCoords * texSize - 0.5;
@ -38,10 +40,10 @@ vec4 textureBicubic(sampler2D sampler, vec2 texCoords) {
offset *= invTexSize.xxyy;
vec4 sample0 = texture(sampler, offset.xz);
vec4 sample1 = texture(sampler, offset.yz);
vec4 sample2 = texture(sampler, offset.xw);
vec4 sample3 = texture(sampler, offset.yw);
vec4 sample0 = texture(tex_sampler, offset.xz);
vec4 sample1 = texture(tex_sampler, offset.yz);
vec4 sample2 = texture(tex_sampler, offset.xw);
vec4 sample3 = texture(tex_sampler, offset.yw);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);

View file

@ -0,0 +1,15 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
precision mediump float;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec4 frag_color;
layout(binding = 0) uniform sampler2D input_texture;
void main() {
frag_color = texture(input_texture, tex_coord);
}

View file

@ -1,12 +1,33 @@
//? #version 330
// MIT License
//
// Copyright (c) 2019 bloc97
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//? #version 430 core
precision mediump float;
in vec2 tex_coord;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec4 frag_color;
out vec4 frag_color;
uniform sampler2D HOOKED;
uniform sampler2D LUMAD;
layout(binding = 0) uniform sampler2D HOOKED;
layout(binding = 1) uniform sampler2D LUMAD;
const float LINE_DETECT_THRESHOLD = 0.4;
const float STRENGTH = 0.6;

View file

@ -1,5 +1,3 @@
//? #version 320 es
// from https://github.com/BreadFish64/ScaleFish/tree/master/scale_force
// MIT License
@ -24,13 +22,14 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//? #version 320 es
precision mediump float;
in vec2 tex_coord;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec4 frag_color;
out vec4 frag_color;
uniform sampler2D input_texture;
layout(binding = 0) uniform sampler2D input_texture;
vec2 tex_size;
vec2 inv_tex_size;

View file

@ -1,5 +1,9 @@
//? #version 330
out vec2 tex_coord;
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
layout(location = 0) out vec2 tex_coord;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));

View file

@ -0,0 +1,41 @@
// MIT License
//
// Copyright (c) 2019 bloc97
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//? #version 430 core
precision mediump float;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec2 frag_color;
layout(binding = 0) uniform sampler2D tex_input;
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
// TODO: improve handling of alpha channel
#define GetLum(xoffset) dot(K, textureLodOffset(tex_input, tex_coord, 0.0, ivec2(xoffset, 0)).rgb)
void main() {
float l = GetLum(-1);
float c = GetLum(0);
float r = GetLum(1);
frag_color = vec2(r - l, l + 2.0 * c + r);
}

View file

@ -1,14 +1,15 @@
//? #version 330
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
//? #version 430 core
precision mediump float;
in vec2 tex_coord;
in vec2 source_size;
in vec2 output_size;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec4 frag_color;
out vec4 frag_color;
uniform sampler2D tex;
uniform lowp float scale;
layout(binding = 0) uniform sampler2D tex;
layout(location = 2) uniform lowp float scale;
const int BLEND_NONE = 0;
const int BLEND_NORMAL = 1;
@ -47,6 +48,7 @@ float GetLeftRatio(vec2 center, vec2 origin, vec2 direction) {
#define P(x, y) textureOffset(tex, coord, ivec2(x, y))
void main() {
vec2 source_size = vec2(textureSize(tex, 0));
vec2 pos = fract(tex_coord * source_size) - vec2(0.5, 0.5);
vec2 coord = tex_coord - pos / source_size;

View file

@ -0,0 +1,39 @@
// MIT License
//
// Copyright (c) 2019 bloc97
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//? #version 430 core
precision mediump float;
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out float frag_color;
layout(binding = 2) uniform sampler2D tex_input;
void main() {
vec2 t = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, 1)).xy;
vec2 c = textureLod(tex_input, tex_coord, 0.0).xy;
vec2 b = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, -1)).xy;
vec2 grad = vec2(t.x + 2.0 * c.x + b.x, b.y - t.y);
frag_color = 1.0 - length(grad);
}

View file

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

View file

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

View file

@ -0,0 +1,73 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/rasterizer_cache/framebuffer_base.h"
#include "video_core/rasterizer_cache/surface_base.h"
#include "video_core/regs.h"
namespace VideoCore {
FramebufferBase::FramebufferBase() = default;
FramebufferBase::FramebufferBase(const Pica::Regs& regs, const SurfaceBase* const color,
u32 color_level, const SurfaceBase* const depth_stencil,
u32 depth_level, Common::Rectangle<u32> surfaces_rect) {
res_scale = color ? color->res_scale : (depth_stencil ? depth_stencil->res_scale : 1u);
// Determine the draw rectangle (render area + scissor)
const Common::Rectangle viewport_rect = regs.rasterizer.GetViewportRect();
draw_rect.left =
std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.left * res_scale,
surfaces_rect.left, surfaces_rect.right);
draw_rect.top =
std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.top * res_scale,
surfaces_rect.bottom, surfaces_rect.top);
draw_rect.right =
std::clamp<s32>(static_cast<s32>(surfaces_rect.left) + viewport_rect.right * res_scale,
surfaces_rect.left, surfaces_rect.right);
draw_rect.bottom =
std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) + viewport_rect.bottom * res_scale,
surfaces_rect.bottom, surfaces_rect.top);
// Update viewport
viewport.x = static_cast<f32>(surfaces_rect.left + viewport_rect.left * res_scale);
viewport.y = static_cast<f32>(surfaces_rect.bottom + viewport_rect.bottom * res_scale);
viewport.width = static_cast<f32>(viewport_rect.GetWidth() * res_scale);
viewport.height = static_cast<f32>(viewport_rect.GetHeight() * res_scale);
// Scissor checks are window-, not viewport-relative, which means that if the cached texture
// sub-rect changes, the scissor bounds also need to be updated.
scissor_rect.left =
static_cast<s32>(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale);
scissor_rect.bottom =
static_cast<s32>(surfaces_rect.bottom + regs.rasterizer.scissor_test.y1 * res_scale);
// x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when
// scaling or doing multisampling.
scissor_rect.right =
static_cast<s32>(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale);
scissor_rect.top =
static_cast<s32>(surfaces_rect.bottom + (regs.rasterizer.scissor_test.y2 + 1) * res_scale);
// Rendering to mipmaps is something quite rare so log it when it occurs.
if (color_level != 0) {
LOG_WARNING(HW_GPU, "Game is rendering to color mipmap {}", color_level);
}
if (depth_level != 0) {
LOG_WARNING(HW_GPU, "Game is rendering to depth mipmap {}", depth_level);
}
// Query surface invalidation intervals
const Common::Rectangle draw_rect_unscaled{draw_rect / res_scale};
if (color) {
color_params = *color;
intervals[0] = color->GetSubRectInterval(draw_rect_unscaled, color_level);
}
if (depth_stencil) {
depth_params = *depth_stencil;
intervals[1] = depth_stencil->GetSubRectInterval(draw_rect_unscaled, depth_level);
}
}
} // namespace VideoCore

View file

@ -0,0 +1,87 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/math_util.h"
#include "video_core/rasterizer_cache/surface_params.h"
namespace Pica {
struct Regs;
}
namespace VideoCore {
class SurfaceBase;
struct ViewportInfo {
f32 x;
f32 y;
f32 width;
f32 height;
};
/**
* A framebuffer is a lightweight abstraction over a pair of surfaces and provides
* metadata about them.
*/
class FramebufferBase {
public:
FramebufferBase();
FramebufferBase(const Pica::Regs& regs, const SurfaceBase* const color, u32 color_level,
const SurfaceBase* const depth_stencil, u32 depth_level,
Common::Rectangle<u32> surfaces_rect);
SurfaceParams ColorParams() const noexcept {
return color_params;
}
SurfaceParams DepthParams() const noexcept {
return depth_params;
}
SurfaceInterval Interval(SurfaceType type) const noexcept {
return intervals[Index(type)];
}
u32 ResolutionScale() const noexcept {
return res_scale;
}
Common::Rectangle<u32> DrawRect() const noexcept {
return draw_rect;
}
Common::Rectangle<s32> Scissor() const noexcept {
return scissor_rect;
}
ViewportInfo Viewport() const noexcept {
return viewport;
}
protected:
u32 Index(VideoCore::SurfaceType type) const noexcept {
switch (type) {
case VideoCore::SurfaceType::Color:
return 0;
case VideoCore::SurfaceType::DepthStencil:
return 1;
default:
LOG_CRITICAL(HW_GPU, "Unknown surface type in framebuffer");
return 0;
}
}
protected:
SurfaceParams color_params{};
SurfaceParams depth_params{};
std::array<SurfaceInterval, 2> intervals{};
Common::Rectangle<s32> scissor_rect{};
Common::Rectangle<u32> draw_rect{};
ViewportInfo viewport;
u32 res_scale{1};
};
} // namespace VideoCore

View file

@ -1,171 +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/alignment.h"
#include "core/memory.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_opengl/gl_vars.h"
#include "video_core/utils.h"
#include "video_core/video_core.h"
namespace OpenGL {
template <bool morton_to_gl, PixelFormat format>
static void MortonCopyTile(u32 stride, u8* tile_buffer, u8* gl_buffer) {
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
constexpr u32 aligned_bytes_per_pixel = GetBytesPerPixel(format);
for (u32 y = 0; y < 8; ++y) {
for (u32 x = 0; x < 8; ++x) {
u8* tile_ptr = tile_buffer + VideoCore::MortonInterleave(x, y) * bytes_per_pixel;
u8* gl_ptr = gl_buffer + ((7 - y) * stride + x) * aligned_bytes_per_pixel;
if constexpr (morton_to_gl) {
if constexpr (format == PixelFormat::D24S8) {
gl_ptr[0] = tile_ptr[3];
std::memcpy(gl_ptr + 1, tile_ptr, 3);
} else if (format == PixelFormat::RGBA8 && GLES) {
// because GLES does not have ABGR format
// so we will do byteswapping here
gl_ptr[0] = tile_ptr[3];
gl_ptr[1] = tile_ptr[2];
gl_ptr[2] = tile_ptr[1];
gl_ptr[3] = tile_ptr[0];
} else if (format == PixelFormat::RGB8 && GLES) {
gl_ptr[0] = tile_ptr[2];
gl_ptr[1] = tile_ptr[1];
gl_ptr[2] = tile_ptr[0];
} else {
std::memcpy(gl_ptr, tile_ptr, bytes_per_pixel);
}
} else {
if constexpr (format == PixelFormat::D24S8) {
std::memcpy(tile_ptr, gl_ptr + 1, 3);
tile_ptr[3] = gl_ptr[0];
} else if (format == PixelFormat::RGBA8 && GLES) {
// because GLES does not have ABGR format
// so we will do byteswapping here
tile_ptr[0] = gl_ptr[3];
tile_ptr[1] = gl_ptr[2];
tile_ptr[2] = gl_ptr[1];
tile_ptr[3] = gl_ptr[0];
} else if (format == PixelFormat::RGB8 && GLES) {
tile_ptr[0] = gl_ptr[2];
tile_ptr[1] = gl_ptr[1];
tile_ptr[2] = gl_ptr[0];
} else {
std::memcpy(tile_ptr, gl_ptr, bytes_per_pixel);
}
}
}
}
}
template <bool morton_to_gl, PixelFormat format>
static void MortonCopy(u32 stride, u32 height, u8* gl_buffer, PAddr base, PAddr start, PAddr end) {
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
constexpr u32 tile_size = bytes_per_pixel * 64;
constexpr u32 aligned_bytes_per_pixel = GetBytesPerPixel(format);
static_assert(aligned_bytes_per_pixel >= bytes_per_pixel, "");
gl_buffer += aligned_bytes_per_pixel - bytes_per_pixel;
const PAddr aligned_down_start = base + Common::AlignDown(start - base, tile_size);
const PAddr aligned_start = base + Common::AlignUp(start - base, tile_size);
const PAddr aligned_end = base + Common::AlignDown(end - base, tile_size);
ASSERT(!morton_to_gl || (aligned_start == start && aligned_end == end));
const u32 begin_pixel_index = (aligned_down_start - base) / bytes_per_pixel;
u32 x = (begin_pixel_index % (stride * 8)) / 8;
u32 y = (begin_pixel_index / (stride * 8)) * 8;
gl_buffer += ((height - 8 - y) * stride + x) * aligned_bytes_per_pixel;
auto glbuf_next_tile = [&] {
x = (x + 8) % stride;
gl_buffer += 8 * aligned_bytes_per_pixel;
if (!x) {
y += 8;
gl_buffer -= stride * 9 * aligned_bytes_per_pixel;
}
};
u8* tile_buffer = VideoCore::g_memory->GetPhysicalPointer(start);
if (start < aligned_start && !morton_to_gl) {
std::array<u8, tile_size> tmp_buf;
MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer);
std::memcpy(tile_buffer, &tmp_buf[start - aligned_down_start],
std::min(aligned_start, end) - start);
tile_buffer += aligned_start - start;
glbuf_next_tile();
}
const u8* const buffer_end = tile_buffer + aligned_end - aligned_start;
PAddr current_paddr = aligned_start;
while (tile_buffer < buffer_end) {
// Pokemon Super Mystery Dungeon will try to use textures that go beyond
// the end address of VRAM. Stop reading if reaches invalid address
if (!VideoCore::g_memory->IsValidPhysicalAddress(current_paddr) ||
!VideoCore::g_memory->IsValidPhysicalAddress(current_paddr + tile_size)) {
LOG_ERROR(Render_OpenGL, "Out of bound texture");
break;
}
MortonCopyTile<morton_to_gl, format>(stride, tile_buffer, gl_buffer);
tile_buffer += tile_size;
current_paddr += tile_size;
glbuf_next_tile();
}
if (end > std::max(aligned_start, aligned_end) && !morton_to_gl) {
std::array<u8, tile_size> tmp_buf;
MortonCopyTile<morton_to_gl, format>(stride, &tmp_buf[0], gl_buffer);
std::memcpy(tile_buffer, &tmp_buf[0], end - aligned_end);
}
}
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> morton_to_gl_fns = {
MortonCopy<true, PixelFormat::RGBA8>, // 0
MortonCopy<true, PixelFormat::RGB8>, // 1
MortonCopy<true, PixelFormat::RGB5A1>, // 2
MortonCopy<true, PixelFormat::RGB565>, // 3
MortonCopy<true, PixelFormat::RGBA4>, // 4
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, // 5 - 13
MortonCopy<true, PixelFormat::D16>, // 14
nullptr, // 15
MortonCopy<true, PixelFormat::D24>, // 16
MortonCopy<true, PixelFormat::D24S8> // 17
};
static constexpr std::array<void (*)(u32, u32, u8*, PAddr, PAddr, PAddr), 18> gl_to_morton_fns = {
MortonCopy<false, PixelFormat::RGBA8>, // 0
MortonCopy<false, PixelFormat::RGB8>, // 1
MortonCopy<false, PixelFormat::RGB5A1>, // 2
MortonCopy<false, PixelFormat::RGB565>, // 3
MortonCopy<false, PixelFormat::RGBA4>, // 4
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, // 5 - 13
MortonCopy<false, PixelFormat::D16>, // 14
nullptr, // 15
MortonCopy<false, PixelFormat::D24>, // 16
MortonCopy<false, PixelFormat::D24S8> // 17
};
} // namespace OpenGL

View file

@ -0,0 +1,154 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/rasterizer_cache/pixel_format.h"
namespace VideoCore {
std::string_view PixelFormatAsString(PixelFormat format) {
switch (format) {
case PixelFormat::RGBA8:
return "RGBA8";
case PixelFormat::RGB8:
return "RGB8";
case PixelFormat::RGB5A1:
return "RGB5A1";
case PixelFormat::RGB565:
return "RGB565";
case PixelFormat::RGBA4:
return "RGBA4";
case PixelFormat::IA8:
return "IA8";
case PixelFormat::RG8:
return "RG8";
case PixelFormat::I8:
return "I8";
case PixelFormat::A8:
return "A8";
case PixelFormat::IA4:
return "IA4";
case PixelFormat::I4:
return "I4";
case PixelFormat::A4:
return "A4";
case PixelFormat::ETC1:
return "ETC1";
case PixelFormat::ETC1A4:
return "ETC1A4";
case PixelFormat::D16:
return "D16";
case PixelFormat::D24:
return "D24";
case PixelFormat::D24S8:
return "D24S8";
default:
return "NotReal";
}
}
bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
SurfaceType source_type = GetFormatType(source_format);
SurfaceType dest_type = GetFormatType(dest_format);
if ((source_type == SurfaceType::Color || source_type == SurfaceType::Texture) &&
(dest_type == SurfaceType::Color || dest_type == SurfaceType::Texture)) {
return true;
}
if (source_type == SurfaceType::Depth && dest_type == SurfaceType::Depth) {
return true;
}
if (source_type == SurfaceType::DepthStencil && dest_type == SurfaceType::DepthStencil) {
return true;
}
LOG_WARNING(HW_GPU, "Unblittable format pair detected {} and {}",
PixelFormatAsString(source_format), PixelFormatAsString(dest_format));
return false;
}
PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
switch (format) {
case Pica::TexturingRegs::TextureFormat::RGBA8:
return PixelFormat::RGBA8;
case Pica::TexturingRegs::TextureFormat::RGB8:
return PixelFormat::RGB8;
case Pica::TexturingRegs::TextureFormat::RGB5A1:
return PixelFormat::RGB5A1;
case Pica::TexturingRegs::TextureFormat::RGB565:
return PixelFormat::RGB565;
case Pica::TexturingRegs::TextureFormat::RGBA4:
return PixelFormat::RGBA4;
case Pica::TexturingRegs::TextureFormat::IA8:
return PixelFormat::IA8;
case Pica::TexturingRegs::TextureFormat::RG8:
return PixelFormat::RG8;
case Pica::TexturingRegs::TextureFormat::I8:
return PixelFormat::I8;
case Pica::TexturingRegs::TextureFormat::A8:
return PixelFormat::A8;
case Pica::TexturingRegs::TextureFormat::IA4:
return PixelFormat::IA4;
case Pica::TexturingRegs::TextureFormat::I4:
return PixelFormat::I4;
case Pica::TexturingRegs::TextureFormat::A4:
return PixelFormat::A4;
case Pica::TexturingRegs::TextureFormat::ETC1:
return PixelFormat::ETC1;
case Pica::TexturingRegs::TextureFormat::ETC1A4:
return PixelFormat::ETC1A4;
default:
return PixelFormat::Invalid;
}
}
PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
switch (format) {
case Pica::FramebufferRegs::ColorFormat::RGBA8:
return PixelFormat::RGBA8;
case Pica::FramebufferRegs::ColorFormat::RGB8:
return PixelFormat::RGB8;
case Pica::FramebufferRegs::ColorFormat::RGB5A1:
return PixelFormat::RGB5A1;
case Pica::FramebufferRegs::ColorFormat::RGB565:
return PixelFormat::RGB565;
case Pica::FramebufferRegs::ColorFormat::RGBA4:
return PixelFormat::RGBA4;
default:
return PixelFormat::Invalid;
}
}
PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
switch (format) {
case Pica::FramebufferRegs::DepthFormat::D16:
return PixelFormat::D16;
case Pica::FramebufferRegs::DepthFormat::D24:
return PixelFormat::D24;
case Pica::FramebufferRegs::DepthFormat::D24S8:
return PixelFormat::D24S8;
default:
return PixelFormat::Invalid;
}
}
PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
switch (format) {
case GPU::Regs::PixelFormat::RGBA8:
return PixelFormat::RGBA8;
case GPU::Regs::PixelFormat::RGB8:
return PixelFormat::RGB8;
case GPU::Regs::PixelFormat::RGB565:
return PixelFormat::RGB565;
case GPU::Regs::PixelFormat::RGB5A1:
return PixelFormat::RGB5A1;
case GPU::Regs::PixelFormat::RGBA4:
return PixelFormat::RGBA4;
default:
return PixelFormat::Invalid;
}
}
} // namespace VideoCore

View file

@ -1,25 +1,23 @@
// Copyright 2022 Citra Emulator Project
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <limits>
#include <string_view>
#include "core/hw/gpu.h"
#include "video_core/regs_framebuffer.h"
#include "video_core/regs_texturing.h"
namespace OpenGL {
namespace VideoCore {
constexpr u32 PIXEL_FORMAT_COUNT = 18;
enum class PixelFormat : u8 {
// First 5 formats are shared between textures and color buffers
enum class PixelFormat : u32 {
RGBA8 = 0,
RGB8 = 1,
RGB5A1 = 2,
RGB565 = 3,
RGBA4 = 4,
// Texture-only formats
IA8 = 5,
RG8 = 6,
I8 = 7,
@ -29,168 +27,88 @@ enum class PixelFormat : u8 {
A4 = 11,
ETC1 = 12,
ETC1A4 = 13,
// Depth buffer-only formats
D16 = 14,
D24 = 16,
D24S8 = 17,
Invalid = 255,
MaxPixelFormat = 18,
Invalid = std::numeric_limits<u32>::max(),
};
constexpr std::size_t PIXEL_FORMAT_COUNT = static_cast<std::size_t>(PixelFormat::MaxPixelFormat);
enum class SurfaceType {
enum class SurfaceType : u32 {
Color = 0,
Texture = 1,
Depth = 2,
DepthStencil = 3,
Fill = 4,
Invalid = 5
Invalid = 5,
};
constexpr std::string_view PixelFormatAsString(PixelFormat format) {
switch (format) {
case PixelFormat::RGBA8:
return "RGBA8";
case PixelFormat::RGB8:
return "RGB8";
case PixelFormat::RGB5A1:
return "RGB5A1";
case PixelFormat::RGB565:
return "RGB565";
case PixelFormat::RGBA4:
return "RGBA4";
case PixelFormat::IA8:
return "IA8";
case PixelFormat::RG8:
return "RG8";
case PixelFormat::I8:
return "I8";
case PixelFormat::A8:
return "A8";
case PixelFormat::IA4:
return "IA4";
case PixelFormat::I4:
return "I4";
case PixelFormat::A4:
return "A4";
case PixelFormat::ETC1:
return "ETC1";
case PixelFormat::ETC1A4:
return "ETC1A4";
case PixelFormat::D16:
return "D16";
case PixelFormat::D24:
return "D24";
case PixelFormat::D24S8:
return "D24S8";
default:
return "NotReal";
}
}
enum class TextureType : u32 {
Texture2D = 0,
CubeMap = 1,
};
constexpr PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format) {
const u32 format_index = static_cast<u32>(format);
return (format_index < 14) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
}
struct PixelFormatInfo {
SurfaceType type;
u32 bits_per_block;
u32 bytes_per_pixel;
};
constexpr PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format) {
const u32 format_index = static_cast<u32>(format);
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
}
constexpr PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format) {
const u32 format_index = static_cast<u32>(format);
return (format_index < 4) ? static_cast<PixelFormat>(format_index + 14) : PixelFormat::Invalid;
}
constexpr PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format) {
const u32 format_index = static_cast<u32>(format);
switch (format) {
// RGB565 and RGB5A1 are switched in PixelFormat compared to ColorFormat
case GPU::Regs::PixelFormat::RGB565:
return PixelFormat::RGB565;
case GPU::Regs::PixelFormat::RGB5A1:
return PixelFormat::RGB5A1;
default:
return (format_index < 5) ? static_cast<PixelFormat>(format) : PixelFormat::Invalid;
}
}
constexpr SurfaceType GetFormatType(PixelFormat pixel_format) {
const u32 format_index = static_cast<u32>(pixel_format);
if (format_index < 5) {
return SurfaceType::Color;
}
if (format_index < 14) {
return SurfaceType::Texture;
}
if (pixel_format == PixelFormat::D16 || pixel_format == PixelFormat::D24) {
return SurfaceType::Depth;
}
if (pixel_format == PixelFormat::D24S8) {
return SurfaceType::DepthStencil;
}
return SurfaceType::Invalid;
}
constexpr bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format) {
SurfaceType source_type = GetFormatType(source_format);
SurfaceType dest_type = GetFormatType(dest_format);
if ((source_type == SurfaceType::Color || source_type == SurfaceType::Texture) &&
(dest_type == SurfaceType::Color || dest_type == SurfaceType::Texture)) {
return true;
}
if (source_type == SurfaceType::Depth && dest_type == SurfaceType::Depth) {
return true;
}
if (source_type == SurfaceType::DepthStencil && dest_type == SurfaceType::DepthStencil) {
return true;
}
return false;
}
/**
* Lookup table for querying pixel format properties (type, name, etc)
* @note Modern GPUs require 4 byte alignment for D24
* @note Texture formats are automatically converted to RGBA8
**/
constexpr std::array<PixelFormatInfo, PIXEL_FORMAT_COUNT> FORMAT_MAP = {{
{SurfaceType::Color, 32, 4},
{SurfaceType::Color, 24, 3},
{SurfaceType::Color, 16, 2},
{SurfaceType::Color, 16, 2},
{SurfaceType::Color, 16, 2},
{SurfaceType::Texture, 16, 4},
{SurfaceType::Texture, 16, 4},
{SurfaceType::Texture, 8, 4},
{SurfaceType::Texture, 8, 4},
{SurfaceType::Texture, 8, 4},
{SurfaceType::Texture, 4, 4},
{SurfaceType::Texture, 4, 4},
{SurfaceType::Texture, 4, 4},
{SurfaceType::Texture, 8, 4},
{SurfaceType::Depth, 16, 2},
{SurfaceType::Invalid, 0, 0},
{SurfaceType::Depth, 24, 4},
{SurfaceType::DepthStencil, 32, 4},
}};
constexpr u32 GetFormatBpp(PixelFormat format) {
switch (format) {
case PixelFormat::RGBA8:
case PixelFormat::D24S8:
return 32;
case PixelFormat::RGB8:
case PixelFormat::D24:
return 24;
case PixelFormat::RGB5A1:
case PixelFormat::RGB565:
case PixelFormat::RGBA4:
case PixelFormat::IA8:
case PixelFormat::RG8:
case PixelFormat::D16:
return 16;
case PixelFormat::I8:
case PixelFormat::A8:
case PixelFormat::IA4:
case PixelFormat::ETC1A4:
return 8;
case PixelFormat::I4:
case PixelFormat::A4:
case PixelFormat::ETC1:
return 4;
default:
return 0;
}
const std::size_t index = static_cast<std::size_t>(format);
ASSERT(index < FORMAT_MAP.size());
return FORMAT_MAP[index].bits_per_block;
}
constexpr u32 GetBytesPerPixel(PixelFormat format) {
// OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
if (format == PixelFormat::D24 || GetFormatType(format) == SurfaceType::Texture) {
return 4;
}
return GetFormatBpp(format) / 8;
constexpr u32 GetFormatBytesPerPixel(PixelFormat format) {
const std::size_t index = static_cast<std::size_t>(format);
ASSERT(index < FORMAT_MAP.size());
return FORMAT_MAP[index].bytes_per_pixel;
}
} // namespace OpenGL
constexpr SurfaceType GetFormatType(PixelFormat format) {
const std::size_t index = static_cast<std::size_t>(format);
ASSERT(index < FORMAT_MAP.size());
return FORMAT_MAP[index].type;
}
std::string_view PixelFormatAsString(PixelFormat format);
bool CheckFormatsBlittable(PixelFormat source_format, PixelFormat dest_format);
PixelFormat PixelFormatFromTextureFormat(Pica::TexturingRegs::TextureFormat format);
PixelFormat PixelFormatFromColorFormat(Pica::FramebufferRegs::ColorFormat format);
PixelFormat PixelFormatFromDepthFormat(Pica::FramebufferRegs::DepthFormat format);
PixelFormat PixelFormatFromGPUPixelFormat(GPU::Regs::PixelFormat format);
} // namespace VideoCore

File diff suppressed because it is too large Load diff

View file

@ -3,17 +3,24 @@
// Refer to the license.txt file included.
#pragma once
#include <unordered_map>
#include "video_core/rasterizer_cache/cached_surface.h"
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
#include <boost/icl/interval_map.hpp>
#include <boost/icl/interval_set.hpp>
#include "video_core/rasterizer_cache/surface_base.h"
#include "video_core/rasterizer_cache/surface_params.h"
#include "video_core/renderer_opengl/gl_texture_runtime.h"
#include "video_core/texture/texture_decode.h"
namespace VideoCore {
class RendererBase;
namespace Memory {
class MemorySystem;
}
namespace OpenGL {
namespace Pica {
struct Regs;
}
namespace VideoCore {
enum class ScaleMatch {
Exact, // only accept same res scale
@ -21,25 +28,59 @@ enum class ScaleMatch {
Ignore // accept every scaled res
};
class TextureDownloaderES;
class TextureFilterer;
class FormatReinterpreterOpenGL;
class RendererBase;
class RasterizerCacheOpenGL : NonCopyable {
class RasterizerCache : NonCopyable {
public:
RasterizerCacheOpenGL(VideoCore::RendererBase& renderer);
~RasterizerCacheOpenGL();
using SurfaceRef = std::shared_ptr<OpenGL::Surface>;
/// Blit one surface's texture to another
bool BlitSurfaces(const Surface& src_surface, const Common::Rectangle<u32>& src_rect,
const Surface& dst_surface, const Common::Rectangle<u32>& dst_rect);
// Declare rasterizer interval types
using SurfaceSet = std::set<SurfaceRef>;
using SurfaceMap = boost::icl::interval_map<PAddr, SurfaceRef, boost::icl::partial_absorber,
std::less, boost::icl::inplace_plus,
boost::icl::inter_section, SurfaceInterval>;
using SurfaceCache = boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber,
std::less, boost::icl::inplace_plus,
boost::icl::inter_section, SurfaceInterval>;
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
"Incorrect interval types");
using SurfaceRect_Tuple = std::tuple<SurfaceRef, Common::Rectangle<u32>>;
using PageMap = boost::icl::interval_map<u32, int>;
struct RenderTargets {
SurfaceRef color_surface;
SurfaceRef depth_surface;
};
struct TextureCube {
SurfaceRef surface{};
std::array<SurfaceRef, 6> faces{};
std::array<u64, 6> ticks{};
};
public:
RasterizerCache(Memory::MemorySystem& memory, OpenGL::TextureRuntime& runtime, Pica::Regs& regs,
RendererBase& renderer);
~RasterizerCache();
/// Perform hardware accelerated texture copy according to the provided configuration
bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config);
/// Perform hardware accelerated display transfer according to the provided configuration
bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config);
/// Perform hardware accelerated memory fill according to the provided configuration
bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config);
/// Copy one surface's region to another
void CopySurface(const Surface& src_surface, const Surface& dst_surface,
void CopySurface(const SurfaceRef& src_surface, const SurfaceRef& dst_surface,
SurfaceInterval copy_interval);
/// Load a texture from 3DS memory to OpenGL and cache it (if not already cached)
Surface GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
SurfaceRef GetSurface(const SurfaceParams& params, ScaleMatch match_res_scale,
bool load_if_create);
/// Attempt to find a subrect (resolution scaled) of a surface, otherwise loads a texture from
@ -48,27 +89,26 @@ public:
bool load_if_create);
/// Get a surface based on the texture configuration
Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
Surface GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
SurfaceRef GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
SurfaceRef GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
/// Get a texture cube based on the texture configuration
const CachedTextureCube& GetTextureCube(const TextureCubeConfig& config);
SurfaceRef GetTextureCube(const TextureCubeConfig& config);
/// Get the color and depth surfaces based on the framebuffer configuration
SurfaceSurfaceRect_Tuple GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb,
const Common::Rectangle<s32>& viewport_rect);
OpenGL::Framebuffer GetFramebufferSurfaces(bool using_color_fb, bool using_depth_fb);
/// Get a surface that matches the fill config
Surface GetFillSurface(const GPU::Regs::MemoryFillConfig& config);
/// Marks the draw rectangle defined in framebuffer as invalid
void InvalidateFramebuffer(const OpenGL::Framebuffer& framebuffer);
/// Get a surface that matches a "texture copy" display transfer config
SurfaceRect_Tuple GetTexCopySurface(const SurfaceParams& params);
/// Write any cached resources overlapping the region back to memory (if dirty)
void FlushRegion(PAddr addr, u32 size, Surface flush_surface = nullptr);
void FlushRegion(PAddr addr, u32 size, SurfaceRef flush_surface = nullptr);
/// Mark region as being invalidated by region_owner (nullptr if 3DS memory)
void InvalidateRegion(PAddr addr, u32 size, const Surface& region_owner);
void InvalidateRegion(PAddr addr, u32 size, const SurfaceRef& region_owner);
/// Flush all cached resources tracked by this cache manager
void FlushAll();
@ -76,61 +116,59 @@ public:
/// Clear all cached resources tracked by this cache manager
void ClearAll(bool flush);
// Textures from destroyed surfaces are stored here to be recyled to reduce allocation overhead
// in the driver
// this must be placed above the surface_cache to ensure all cached surfaces are destroyed
// before destroying the recycler
std::unordered_multimap<HostTextureTag, OGLTexture> host_texture_recycler;
private:
void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
/// Transfers ownership of a memory region from src_surface to dest_surface
void DuplicateSurface(const SurfaceRef& src_surface, const SurfaceRef& dest_surface);
/// Update surface's texture for given region when necessary
void ValidateSurface(const Surface& surface, PAddr addr, u32 size);
void ValidateSurface(const SurfaceRef& surface, PAddr addr, u32 size);
// Returns false if there is a surface in the cache at the interval with the same bit-width,
bool NoUnimplementedReinterpretations(const OpenGL::Surface& surface,
OpenGL::SurfaceParams& params,
const OpenGL::SurfaceInterval& interval);
/// Copies pixel data in interval from the guest VRAM to the host GPU surface
void UploadSurface(const SurfaceRef& surface, SurfaceInterval interval);
// Return true if a surface with an invalid pixel format exists at the interval
bool IntervalHasInvalidPixelFormat(SurfaceParams& params, const SurfaceInterval& interval);
/// Copies pixel data in interval from the host GPU surface to the guest VRAM
void DownloadSurface(const SurfaceRef& surface, SurfaceInterval interval);
// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
bool ValidateByReinterpretation(const Surface& surface, SurfaceParams& params,
/// Downloads a fill surface to guest VRAM
void DownloadFillSurface(const SurfaceRef& surface, SurfaceInterval interval);
/// Returns false if there is a surface in the cache at the interval with the same bit-width,
bool NoUnimplementedReinterpretations(const SurfaceRef& surface, SurfaceParams params,
const SurfaceInterval& interval);
/// Return true if a surface with an invalid pixel format exists at the interval
bool IntervalHasInvalidPixelFormat(const SurfaceParams& params,
const SurfaceInterval& interval);
/// Attempt to find a reinterpretable surface in the cache and use it to copy for validation
bool ValidateByReinterpretation(const SurfaceRef& surface, SurfaceParams params,
const SurfaceInterval& interval);
/// Create a new surface
Surface CreateSurface(const SurfaceParams& params);
SurfaceRef CreateSurface(const SurfaceParams& params);
/// Register surface into the cache
void RegisterSurface(const Surface& surface);
void RegisterSurface(const SurfaceRef& surface);
/// Remove surface from the cache
void UnregisterSurface(const Surface& surface);
void UnregisterSurface(const SurfaceRef& surface);
/// Increase/decrease the number of surface in pages touching the specified region
void UpdatePagesCachedCount(PAddr addr, u32 size, int delta);
VideoCore::RendererBase& renderer;
TextureRuntime runtime;
private:
Memory::MemorySystem& memory;
OpenGL::TextureRuntime& runtime;
Pica::Regs& regs;
RendererBase& renderer;
SurfaceCache surface_cache;
PageMap cached_pages;
SurfaceMap dirty_regions;
SurfaceSet remove_surfaces;
u16 resolution_scale_factor;
std::unordered_map<TextureCubeConfig, CachedTextureCube> texture_cube_cache;
std::recursive_mutex mutex;
public:
OGLTexture AllocateSurfaceTexture(const FormatTuple& format_tuple, u32 width, u32 height);
std::unique_ptr<TextureFilterer> texture_filterer;
std::unique_ptr<FormatReinterpreterOpenGL> format_reinterpreter;
std::unique_ptr<TextureDownloaderES> texture_downloader_es;
std::vector<SurfaceRef> remove_surfaces;
u32 resolution_scale_factor;
RenderTargets render_targets;
std::unordered_map<TextureCubeConfig, TextureCube> texture_cube_cache;
bool use_filter{};
};
} // namespace OpenGL
} // namespace VideoCore

View file

@ -1,38 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <set>
#include <tuple>
#include <boost/icl/interval_map.hpp>
#include <boost/icl/interval_set.hpp>
#include "common/common_types.h"
#include "common/math_util.h"
namespace OpenGL {
class CachedSurface;
using Surface = std::shared_ptr<CachedSurface>;
// Declare rasterizer interval types
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
using SurfaceSet = std::set<Surface>;
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
using SurfaceMap =
boost::icl::interval_map<PAddr, Surface, boost::icl::partial_absorber, std::less,
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
using SurfaceCache =
boost::icl::interval_map<PAddr, SurfaceSet, boost::icl::partial_absorber, std::less,
boost::icl::inplace_plus, boost::icl::inter_section, SurfaceInterval>;
static_assert(std::is_same<SurfaceRegions::interval_type, SurfaceCache::interval_type>() &&
std::is_same<SurfaceMap::interval_type, SurfaceCache::interval_type>(),
"Incorrect interval types");
using SurfaceRect_Tuple = std::tuple<Surface, Common::Rectangle<u32>>;
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, Common::Rectangle<u32>>;
using PageMap = boost::icl::interval_map<u32, int>;
} // namespace OpenGL

View file

@ -1,56 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <glad/glad.h>
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
#include "video_core/renderer_opengl/gl_vars.h"
namespace OpenGL {
constexpr FormatTuple tex_tuple = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
static constexpr std::array<FormatTuple, 4> depth_format_tuples = {{
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, // D16
{},
{GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, // D24
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, // D24S8
}};
static constexpr std::array<FormatTuple, 5> fb_format_tuples = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8}, // RGBA8
{GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE}, // RGB8
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
}};
// Same as above, with minor changes for OpenGL ES. Replaced
// GL_UNSIGNED_INT_8_8_8_8 with GL_UNSIGNED_BYTE and
// GL_BGR with GL_RGB
static constexpr std::array<FormatTuple, 5> fb_format_tuples_oes = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
{GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, // RGB8
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // RGB5A1
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
}};
const FormatTuple& GetFormatTuple(PixelFormat pixel_format) {
const SurfaceType type = GetFormatType(pixel_format);
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
if (type == SurfaceType::Color) {
ASSERT(format_index < fb_format_tuples.size());
return (GLES ? fb_format_tuples_oes : fb_format_tuples)[format_index];
} else if (type == SurfaceType::Depth || type == SurfaceType::DepthStencil) {
const std::size_t tuple_idx = format_index - 14;
ASSERT(tuple_idx < depth_format_tuples.size());
return depth_format_tuples[tuple_idx];
}
return tex_tuple;
}
} // namespace OpenGL

View file

@ -1,73 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include "common/hash.h"
#include "video_core/rasterizer_cache/pixel_format.h"
namespace OpenGL {
struct FormatTuple {
int internal_format;
u32 format;
u32 type;
};
const FormatTuple& GetFormatTuple(PixelFormat pixel_format);
struct HostTextureTag {
FormatTuple format_tuple{};
u32 width = 0;
u32 height = 0;
bool operator==(const HostTextureTag& rhs) const noexcept {
return std::memcmp(this, &rhs, sizeof(HostTextureTag)) == 0;
};
const u64 Hash() const {
return Common::ComputeHash64(this, sizeof(HostTextureTag));
}
};
struct TextureCubeConfig {
PAddr px;
PAddr nx;
PAddr py;
PAddr ny;
PAddr pz;
PAddr nz;
u32 width;
Pica::TexturingRegs::TextureFormat format;
bool operator==(const TextureCubeConfig& rhs) const {
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) == 0;
}
bool operator!=(const TextureCubeConfig& rhs) const {
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) != 0;
}
const u64 Hash() const {
return Common::ComputeHash64(this, sizeof(TextureCubeConfig));
}
};
} // namespace OpenGL
namespace std {
template <>
struct hash<OpenGL::HostTextureTag> {
std::size_t operator()(const OpenGL::HostTextureTag& tag) const noexcept {
return tag.Hash();
}
};
template <>
struct hash<OpenGL::TextureCubeConfig> {
std::size_t operator()(const OpenGL::TextureCubeConfig& config) const noexcept {
return config.Hash();
}
};
} // namespace std

View file

@ -0,0 +1,156 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/alignment.h"
#include "video_core/rasterizer_cache/surface_base.h"
#include "video_core/texture/texture_decode.h"
namespace VideoCore {
SurfaceBase::SurfaceBase(const SurfaceParams& params) : SurfaceParams{params} {}
SurfaceBase::~SurfaceBase() = default;
bool SurfaceBase::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 SurfaceBase::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;
}
SurfaceInterval SurfaceBase::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 (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;
}
ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format) {
const SurfaceType dst_type = GetFormatType(dst_format);
const std::array fill_buffer = MakeFillBuffer(copy_addr);
ClearValue result{};
switch (dst_type) {
case SurfaceType::Color:
case SurfaceType::Texture:
case SurfaceType::Fill: {
Pica::Texture::TextureInfo tex_info{};
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(dst_format);
const auto color = Pica::Texture::LookupTexture(fill_buffer.data(), 0, 0, tex_info);
result.color = color / 255.f;
break;
}
case SurfaceType::Depth: {
u32 depth_uint = 0;
if (dst_format == PixelFormat::D16) {
std::memcpy(&depth_uint, fill_buffer.data(), 2);
result.depth = depth_uint / 65535.0f; // 2^16 - 1
} else if (dst_format == PixelFormat::D24) {
std::memcpy(&depth_uint, fill_buffer.data(), 3);
result.depth = depth_uint / 16777215.0f; // 2^24 - 1
}
break;
}
case SurfaceType::DepthStencil: {
u32 clear_value_uint;
std::memcpy(&clear_value_uint, fill_buffer.data(), sizeof(u32));
result.depth = (clear_value_uint & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
result.stencil = (clear_value_uint >> 24);
break;
}
default:
UNREACHABLE_MSG("Invalid surface type!");
}
return result;
}
std::array<u8, 4> SurfaceBase::MakeFillBuffer(PAddr copy_addr) {
const PAddr fill_offset = (copy_addr - addr) % fill_size;
std::array<u8, 4> fill_buffer;
u32 fill_buff_pos = fill_offset;
for (std::size_t i = 0; i < fill_buffer.size(); i++) {
fill_buffer[i] = fill_data[fill_buff_pos++ % fill_size];
}
return fill_buffer;
}
} // namespace VideoCore

View file

@ -0,0 +1,66 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <boost/icl/interval_set.hpp>
#include "video_core/rasterizer_cache/surface_params.h"
namespace VideoCore {
using SurfaceRegions = boost::icl::interval_set<PAddr, std::less, SurfaceInterval>;
class SurfaceBase : public SurfaceParams {
public:
SurfaceBase(const SurfaceParams& params);
~SurfaceBase();
/// 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 surface can validate copy_interval of dest_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;
/// Returns the clear value used to validate another surface from this fill surface
ClearValue MakeClearValue(PAddr copy_addr, PixelFormat dst_format);
u64 ModificationTick() const noexcept {
return modification_tick;
}
bool IsRegionValid(SurfaceInterval interval) const {
return (invalid_regions.find(interval) == invalid_regions.end());
}
void MarkValid(SurfaceInterval interval) {
invalid_regions.erase(interval);
modification_tick++;
}
void MarkInvalid(SurfaceInterval interval) {
invalid_regions.insert(interval);
modification_tick++;
}
bool IsFullyInvalid() const {
auto interval = GetInterval();
return *invalid_regions.equal_range(interval).first == interval;
}
private:
/// Returns the fill buffer value starting from copy_addr
std::array<u8, 4> MakeFillBuffer(PAddr copy_addr);
public:
bool registered = false;
SurfaceRegions invalid_regions;
u32 fill_size = 0;
std::array<u8, 4> fill_data;
u64 modification_tick = 1;
};
} // namespace VideoCore

View file

@ -3,133 +3,9 @@
// Refer to the license.txt file included.
#include "common/alignment.h"
#include "video_core/rasterizer_cache/rasterizer_cache.h"
#include "video_core/rasterizer_cache/surface_params.h"
namespace OpenGL {
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
SurfaceParams params = *this;
const u32 tiled_size = is_tiled ? 8 : 1;
const u32 stride_tiled_bytes = BytesInPixels(stride * tiled_size);
PAddr aligned_start =
addr + Common::AlignDown(boost::icl::first(interval) - addr, stride_tiled_bytes);
PAddr aligned_end =
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, stride_tiled_bytes);
if (aligned_end - aligned_start > stride_tiled_bytes) {
params.addr = aligned_start;
params.height = (aligned_end - aligned_start) / BytesInPixels(stride);
} else {
// 1 row
ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
const u32 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
aligned_start =
addr + Common::AlignDown(boost::icl::first(interval) - addr, tiled_alignment);
aligned_end =
addr + Common::AlignUp(boost::icl::last_next(interval) - addr, tiled_alignment);
params.addr = aligned_start;
params.width = PixelsInBytes(aligned_end - aligned_start) / tiled_size;
params.stride = params.width;
params.height = tiled_size;
}
params.UpdateParams();
return params;
}
SurfaceInterval SurfaceParams::GetSubRectInterval(Common::Rectangle<u32> unscaled_rect) const {
if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) {
return {};
}
if (is_tiled) {
unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8;
unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8;
unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8;
unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8;
}
const u32 stride_tiled = !is_tiled ? stride : stride * 8;
const u32 pixel_offset =
stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) +
unscaled_rect.left;
const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth();
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);
if (is_tiled) {
const int x0 = (begin_pixel_index % (stride * 8)) / 8;
const int y0 = (begin_pixel_index / (stride * 8)) * 8;
// Top to bottom
return Common::Rectangle<u32>(x0, height - y0, x0 + sub_surface.width,
height - (y0 + sub_surface.height));
}
const int x0 = begin_pixel_index % stride;
const int y0 = begin_pixel_index / stride;
// Bottom to top
return Common::Rectangle<u32>(x0, y0 + sub_surface.height, x0 + sub_surface.width, y0);
}
Common::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
auto rect = GetSubRect(sub_surface);
rect.left = rect.left * res_scale;
rect.right = rect.right * res_scale;
rect.top = rect.top * res_scale;
rect.bottom = rect.bottom * res_scale;
return rect;
}
namespace VideoCore {
bool SurfaceParams::ExactMatch(const SurfaceParams& other_surface) const {
return std::tie(other_surface.addr, other_surface.width, other_surface.height,
@ -157,6 +33,7 @@ bool SurfaceParams::CanExpand(const SurfaceParams& expanded_surface) const {
}
bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
const SurfaceInterval copy_interval = texcopy_params.GetInterval();
if (pixel_format == PixelFormat::Invalid || addr > texcopy_params.addr ||
end < texcopy_params.end) {
return false;
@ -170,7 +47,180 @@ bool SurfaceParams::CanTexCopy(const SurfaceParams& texcopy_params) const {
((texcopy_params.addr - addr) % tile_stride) + texcopy_params.width <= tile_stride;
}
return FromInterval(texcopy_params.GetInterval()).GetInterval() == texcopy_params.GetInterval();
const u32 target_level = LevelOf(texcopy_params.addr);
if ((LevelInterval(target_level) & copy_interval) != copy_interval) {
return false;
}
return FromInterval(copy_interval).GetInterval() == copy_interval;
}
} // namespace OpenGL
void SurfaceParams::UpdateParams() {
if (stride == 0) {
stride = width;
}
type = GetFormatType(pixel_format);
if (levels != 1) {
ASSERT(stride == width);
CalculateMipLevelOffsets();
size = CalculateSurfaceSize();
} else {
mipmap_offsets[0] = addr;
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
}
end = addr + size;
}
Common::Rectangle<u32> SurfaceParams::GetSubRect(const SurfaceParams& sub_surface) const {
const u32 level = LevelOf(sub_surface.addr);
const u32 begin_pixel_index = PixelsInBytes(sub_surface.addr - mipmap_offsets[level]);
ASSERT(stride == width || level == 0);
const u32 stride_lod = stride >> level;
if (is_tiled) {
const u32 x0 = (begin_pixel_index % (stride_lod * 8)) / 8;
const u32 y0 = (begin_pixel_index / (stride_lod * 8)) * 8;
const u32 height_lod = height >> level;
// Top to bottom
return {x0, height_lod - y0, x0 + sub_surface.width,
height_lod - (y0 + sub_surface.height)};
}
const u32 x0 = begin_pixel_index % stride_lod;
const u32 y0 = begin_pixel_index / stride_lod;
// Bottom to top
return {x0, y0 + sub_surface.height, x0 + sub_surface.width, y0};
}
Common::Rectangle<u32> SurfaceParams::GetScaledSubRect(const SurfaceParams& sub_surface) const {
return GetSubRect(sub_surface) * res_scale;
}
SurfaceParams SurfaceParams::FromInterval(SurfaceInterval interval) const {
SurfaceParams params = *this;
const u32 level = LevelOf(interval.lower());
const PAddr end_addr = interval.upper();
// Ensure provided interval is contained in a single level
ASSERT(level == LevelOf(end_addr) || end_addr == end || end_addr == mipmap_offsets[level + 1]);
params.width >>= level;
params.stride >>= level;
const u32 tiled_size = is_tiled ? 8 : 1;
const u32 stride_tiled_bytes = BytesInPixels(params.stride * tiled_size);
ASSERT(stride == width || level == 0);
const PAddr start = mipmap_offsets[level];
PAddr aligned_start =
start + Common::AlignDown(boost::icl::first(interval) - start, stride_tiled_bytes);
PAddr aligned_end =
start + Common::AlignUp(boost::icl::last_next(interval) - start, stride_tiled_bytes);
if (aligned_end - aligned_start > stride_tiled_bytes) {
params.addr = aligned_start;
params.height = (aligned_end - aligned_start) / BytesInPixels(params.stride);
} else {
// 1 row
ASSERT(aligned_end - aligned_start == stride_tiled_bytes);
const u32 tiled_alignment = BytesInPixels(is_tiled ? 8 * 8 : 1);
aligned_start =
start + Common::AlignDown(boost::icl::first(interval) - start, tiled_alignment);
aligned_end =
start + Common::AlignUp(boost::icl::last_next(interval) - start, tiled_alignment);
params.addr = aligned_start;
params.width = PixelsInBytes(aligned_end - aligned_start) / tiled_size;
params.stride = params.width;
params.height = tiled_size;
}
params.levels = 1;
params.UpdateParams();
return params;
}
SurfaceInterval SurfaceParams::GetSubRectInterval(Common::Rectangle<u32> unscaled_rect,
u32 level) const {
if (unscaled_rect.GetHeight() == 0 || unscaled_rect.GetWidth() == 0) [[unlikely]] {
return {};
}
if (is_tiled) {
unscaled_rect.left = Common::AlignDown(unscaled_rect.left, 8) * 8;
unscaled_rect.bottom = Common::AlignDown(unscaled_rect.bottom, 8) / 8;
unscaled_rect.right = Common::AlignUp(unscaled_rect.right, 8) * 8;
unscaled_rect.top = Common::AlignUp(unscaled_rect.top, 8) / 8;
}
const u32 stride_tiled = (!is_tiled ? stride : stride * 8) >> level;
const u32 pixels = (unscaled_rect.GetHeight() - 1) * stride_tiled + unscaled_rect.GetWidth();
const u32 pixel_offset =
stride_tiled * (!is_tiled ? unscaled_rect.bottom : (height / 8) - unscaled_rect.top) +
unscaled_rect.left;
const PAddr start = mipmap_offsets[level];
return {start + BytesInPixels(pixel_offset), start + BytesInPixels(pixel_offset + pixels)};
}
void SurfaceParams::CalculateMipLevelOffsets() {
ASSERT(levels <= MAX_PICA_LEVELS && stride == width);
u32 level_width = width;
u32 level_height = height;
u32 offset = addr;
for (u32 level = 0; level < levels; level++) {
mipmap_offsets[level] = offset;
offset += BytesInPixels(level_width * level_height);
level_width >>= 1;
level_height >>= 1;
}
}
u32 SurfaceParams::CalculateSurfaceSize() const {
ASSERT(levels <= MAX_PICA_LEVELS && stride == width);
u32 level_width = width;
u32 level_height = height;
u32 size = 0;
for (u32 level = 0; level < levels; level++) {
size += BytesInPixels(level_width * level_height);
level_width >>= 1;
level_height >>= 1;
}
return size;
}
SurfaceInterval SurfaceParams::LevelInterval(u32 level) const {
ASSERT(levels > level);
const PAddr start_addr = mipmap_offsets[level];
const PAddr end_addr = level == (levels - 1) ? end : mipmap_offsets[level + 1];
return {start_addr, end_addr};
}
u32 SurfaceParams::LevelOf(PAddr level_addr) const {
ASSERT(level_addr >= addr && level_addr <= end);
u32 level = levels - 1;
while (mipmap_offsets[level] > level_addr) {
level--;
}
return level;
}
std::string SurfaceParams::DebugName(bool scaled) const noexcept {
const u32 scaled_width = scaled ? GetScaledWidth() : width;
const u32 scaled_height = scaled ? GetScaledHeight() : height;
return fmt::format("Surface: {}x{} {} {} levels from {:#x} to {:#x} ({})", scaled_width,
scaled_height, PixelFormatAsString(pixel_format), levels, addr, end,
scaled ? "scaled" : "unscaled");
}
} // namespace VideoCore

View file

@ -4,75 +4,89 @@
#pragma once
#include <array>
#include <climits>
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/rasterizer_cache/rasterizer_cache_types.h"
#include "video_core/rasterizer_cache/utils.h"
namespace OpenGL {
namespace VideoCore {
constexpr std::size_t MAX_PICA_LEVELS = 8;
class SurfaceParams {
public:
// Surface match traits
/// Returns true if other_surface matches exactly params
bool ExactMatch(const SurfaceParams& other_surface) const;
/// Returns true if sub_surface is a subrect of params
bool CanSubRect(const SurfaceParams& sub_surface) const;
/// Returns true if params can be expanded to match expanded_surface
bool CanExpand(const SurfaceParams& expanded_surface) const;
/// Returns true if params can be used for texcopy
bool CanTexCopy(const SurfaceParams& texcopy_params) const;
/// Updates remaining members from the already set addr, width, height and pixel_format
void UpdateParams();
/// Returns the unscaled rectangle referenced by sub_surface
Common::Rectangle<u32> GetSubRect(const SurfaceParams& sub_surface) const;
/// Returns the scaled rectangle referenced by sub_surface
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;
/// Returns the address interval referenced by unscaled_rect
SurfaceInterval GetSubRectInterval(Common::Rectangle<u32> unscaled_rect, u32 level = 0) const;
/// Updates remaining members from the already set addr, width, height and pixel_format
void UpdateParams() {
if (stride == 0) {
stride = width;
/// Return the address interval of the provided level
SurfaceInterval LevelInterval(u32 level) const;
/// Returns the level of the provided address
u32 LevelOf(PAddr addr) const;
/// Returns a string identifier of the params object
std::string DebugName(bool scaled) const noexcept;
[[nodiscard]] SurfaceInterval GetInterval() const noexcept {
return SurfaceInterval{addr, end};
}
type = GetFormatType(pixel_format);
size = !is_tiled ? BytesInPixels(stride * (height - 1) + width)
: BytesInPixels(stride * 8 * (height / 8 - 1) + width * 8);
end = addr + size;
[[nodiscard]] u32 GetFormatBpp() const noexcept {
return VideoCore::GetFormatBpp(pixel_format);
}
SurfaceInterval GetInterval() const {
return SurfaceInterval(addr, end);
}
u32 GetFormatBpp() const {
return OpenGL::GetFormatBpp(pixel_format);
}
u32 GetScaledWidth() const {
[[nodiscard]] u32 GetScaledWidth() const noexcept {
return width * res_scale;
}
u32 GetScaledHeight() const {
[[nodiscard]] u32 GetScaledHeight() const noexcept {
return height * res_scale;
}
Common::Rectangle<u32> GetRect() const {
[[nodiscard]] Common::Rectangle<u32> GetRect() const noexcept {
return {0, height, width, 0};
}
Common::Rectangle<u32> GetScaledRect() const {
[[nodiscard]] Common::Rectangle<u32> GetScaledRect() const noexcept {
return {0, GetScaledHeight(), GetScaledWidth(), 0};
}
u32 PixelsInBytes(u32 size) const {
[[nodiscard]] u32 PixelsInBytes(u32 size) const noexcept {
return size * 8 / GetFormatBpp();
}
u32 BytesInPixels(u32 pixels) const {
[[nodiscard]] u32 BytesInPixels(u32 pixels) const noexcept {
return pixels * GetFormatBpp() / 8;
}
private:
/// Computes the offset of each mipmap level
void CalculateMipLevelOffsets();
/// Calculates total surface size taking mipmaps into account
u32 CalculateSurfaceSize() const;
public:
PAddr addr = 0;
PAddr end = 0;
@ -81,11 +95,15 @@ public:
u32 width = 0;
u32 height = 0;
u32 stride = 0;
u16 res_scale = 1;
u32 levels = 1;
u32 res_scale = 1;
bool is_tiled = false;
TextureType texture_type = TextureType::Texture2D;
PixelFormat pixel_format = PixelFormat::Invalid;
SurfaceType type = SurfaceType::Invalid;
std::array<u32, MAX_PICA_LEVELS> mipmap_offsets{};
};
} // namespace OpenGL
} // namespace VideoCore

View file

@ -0,0 +1,548 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <algorithm>
#include <bit>
#include <span>
#include "common/alignment.h"
#include "common/color.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/texture/etc1.h"
#include "video_core/utils.h"
namespace VideoCore {
template <typename T>
inline T MakeInt(const u8* bytes) {
T integer{};
std::memcpy(&integer, bytes, sizeof(T));
return integer;
}
template <PixelFormat format, bool converted>
constexpr void DecodePixel(const u8* source, u8* dest) {
using namespace Common::Color;
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
if constexpr (format == PixelFormat::RGBA8 && converted) {
const auto abgr = DecodeRGBA8(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::RGB8 && converted) {
const auto abgr = DecodeRGB8(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::RGB565 && converted) {
const auto abgr = DecodeRGB565(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::RGB5A1 && converted) {
const auto abgr = DecodeRGB5A1(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::RGBA4 && converted) {
const auto abgr = DecodeRGBA4(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::IA8) {
const auto abgr = DecodeIA8(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::RG8) {
const auto abgr = DecodeRG8(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::I8) {
const auto abgr = DecodeI8(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::A8) {
const auto abgr = DecodeA8(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::IA4) {
const auto abgr = DecodeIA4(source);
std::memcpy(dest, abgr.AsArray(), 4);
} else if constexpr (format == PixelFormat::D24 && converted) {
const auto d32 = DecodeD24(source) / 16777215.f;
std::memcpy(dest, &d32, sizeof(d32));
} else if constexpr (format == PixelFormat::D24S8) {
const u32 d24s8 = std::rotl(MakeInt<u32>(source), 8);
std::memcpy(dest, &d24s8, sizeof(u32));
} else {
std::memcpy(dest, source, bytes_per_pixel);
}
}
template <PixelFormat format>
constexpr void DecodePixel4(u32 x, u32 y, const u8* source_tile, u8* dest_pixel) {
const u32 morton_offset = VideoCore::MortonInterleave(x, y);
const u8 value = source_tile[morton_offset >> 1];
const u8 pixel = Common::Color::Convert4To8((morton_offset % 2) ? (value >> 4) : (value & 0xF));
if constexpr (format == PixelFormat::I4) {
std::memset(dest_pixel, pixel, 3);
dest_pixel[3] = 255;
} else {
std::memset(dest_pixel, 0, 3);
dest_pixel[3] = pixel;
}
}
template <PixelFormat format>
constexpr void DecodePixelETC1(u32 x, u32 y, const u8* source_tile, u8* dest_pixel) {
constexpr u32 subtile_width = 4;
constexpr u32 subtile_height = 4;
constexpr bool has_alpha = format == PixelFormat::ETC1A4;
constexpr std::size_t subtile_size = has_alpha ? 16 : 8;
const u32 subtile_index = (x / subtile_width) + 2 * (y / subtile_height);
x %= subtile_width;
y %= subtile_height;
const u8* subtile_ptr = source_tile + subtile_index * subtile_size;
u8 alpha = 255;
if constexpr (has_alpha) {
u64_le packed_alpha;
std::memcpy(&packed_alpha, subtile_ptr, sizeof(u64));
subtile_ptr += sizeof(u64);
alpha = Common::Color::Convert4To8((packed_alpha >> (4 * (x * subtile_width + y))) & 0xF);
}
const u64_le subtile_data = MakeInt<u64_le>(subtile_ptr);
const auto rgb = Pica::Texture::SampleETC1Subtile(subtile_data, x, y);
// Copy the uncompressed pixel to the destination
std::memcpy(dest_pixel, rgb.AsArray(), 3);
dest_pixel[3] = alpha;
}
template <PixelFormat format, bool converted>
constexpr void EncodePixel(const u8* source, u8* dest) {
using namespace Common::Color;
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
if constexpr (format == PixelFormat::RGBA8 && converted) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeRGBA8(rgba, dest);
} else if constexpr (format == PixelFormat::RGB8 && converted) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeRGB8(rgba, dest);
} else if constexpr (format == PixelFormat::RGB565 && converted) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeRGB565(rgba, dest);
} else if constexpr (format == PixelFormat::RGB5A1 && converted) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeRGB5A1(rgba, dest);
} else if constexpr (format == PixelFormat::RGBA4 && converted) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeRGBA4(rgba, dest);
} else if constexpr (format == PixelFormat::IA8) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeIA8(rgba, dest);
} else if constexpr (format == PixelFormat::RG8) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeRG8(rgba, dest);
} else if constexpr (format == PixelFormat::I8) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeI8(rgba, dest);
} else if constexpr (format == PixelFormat::A8) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeA8(rgba, dest);
} else if constexpr (format == PixelFormat::IA4) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source, 4);
EncodeIA4(rgba, dest);
} else if constexpr (format == PixelFormat::D24 && converted) {
float d32;
std::memcpy(&d32, source, sizeof(d32));
EncodeD24(d32 * 0xFFFFFF, dest);
} else if constexpr (format == PixelFormat::D24S8) {
const u32 s8d24 = std::rotr(MakeInt<u32>(source), 8);
std::memcpy(dest, &s8d24, sizeof(u32));
} else {
std::memcpy(dest, source, bytes_per_pixel);
}
}
template <PixelFormat format>
constexpr void EncodePixel4(u32 x, u32 y, const u8* source_pixel, u8* dest_tile_buffer) {
Common::Vec4<u8> rgba;
std::memcpy(rgba.AsArray(), source_pixel, 4);
u8 pixel;
if constexpr (format == PixelFormat::I4) {
pixel = Common::Color::AverageRgbComponents(rgba);
} else {
pixel = rgba.a();
}
const u32 morton_offset = VideoCore::MortonInterleave(x, y);
const u32 byte_offset = morton_offset >> 1;
const u8 current_values = dest_tile_buffer[byte_offset];
const u8 new_value = Common::Color::Convert8To4(pixel);
if (morton_offset % 2) {
dest_tile_buffer[byte_offset] = (new_value << 4) | (current_values & 0x0F);
} else {
dest_tile_buffer[byte_offset] = (current_values & 0xF0) | new_value;
}
}
template <bool morton_to_linear, PixelFormat format, bool converted>
constexpr void MortonCopyTile(u32 stride, std::span<u8> tile_buffer, std::span<u8> linear_buffer) {
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
constexpr u32 linear_bytes_per_pixel = converted ? 4 : GetFormatBytesPerPixel(format);
constexpr bool is_compressed = format == PixelFormat::ETC1 || format == PixelFormat::ETC1A4;
constexpr bool is_4bit = format == PixelFormat::I4 || format == PixelFormat::A4;
for (u32 y = 0; y < 8; y++) {
for (u32 x = 0; x < 8; x++) {
const auto tiled_pixel = tile_buffer.subspan(
VideoCore::MortonInterleave(x, y) * bytes_per_pixel, bytes_per_pixel);
const auto linear_pixel = linear_buffer.subspan(
((7 - y) * stride + x) * linear_bytes_per_pixel, linear_bytes_per_pixel);
if constexpr (morton_to_linear) {
if constexpr (is_compressed) {
DecodePixelETC1<format>(x, y, tile_buffer.data(), linear_pixel.data());
} else if constexpr (is_4bit) {
DecodePixel4<format>(x, y, tile_buffer.data(), linear_pixel.data());
} else {
DecodePixel<format, converted>(tiled_pixel.data(), linear_pixel.data());
}
} else {
if constexpr (is_4bit) {
EncodePixel4<format>(x, y, linear_pixel.data(), tile_buffer.data());
} else {
EncodePixel<format, converted>(linear_pixel.data(), tiled_pixel.data());
}
}
}
}
}
/**
* @brief Performs morton to/from linear convertions on the provided pixel data
* @param converted If true performs RGBA8 to/from convertion to all color formats
* @param width, height The dimentions of the rectangular region of pixels in linear_buffer
* @param start_offset The number of bytes from the start of the first tile to the start of
* tiled_buffer
* @param end_offset The number of bytes from the start of the first tile to the end of tiled_buffer
* @param linear_buffer The linear pixel data
* @param tiled_buffer The tiled pixel data
*
* The MortonCopy is at the heart of the PICA texture implementation, as it's responsible for
* converting between linear and morton tiled layouts. The function handles both convertions but
* there are slightly different paths and inputs for each:
*
* Morton to Linear:
* During uploads, tiled_buffer is always aligned to the tile or scanline boundary depending if the
* linear rectangle spans multiple vertical tiles. linear_buffer does not reference the entire
* texture area, but rather the specific rectangle affected by the upload.
*
* Linear to Morton:
* This is similar to the other convertion but with some differences. In this case tiled_buffer is
* not required to be aligned to any specific boundary which requires special care.
* start_offset/end_offset are useful here as they tell us exactly where the data should be placed
* in the linear_buffer.
*/
template <bool morton_to_linear, PixelFormat format, bool converted = false>
static constexpr void MortonCopy(u32 width, u32 height, u32 start_offset, u32 end_offset,
std::span<u8> linear_buffer, std::span<u8> tiled_buffer) {
constexpr u32 bytes_per_pixel = GetFormatBpp(format) / 8;
constexpr u32 aligned_bytes_per_pixel = converted ? 4 : GetFormatBytesPerPixel(format);
constexpr u32 tile_size = GetFormatBpp(format) * 64 / 8;
static_assert(aligned_bytes_per_pixel >= bytes_per_pixel, "");
const u32 linear_tile_stride = (7 * width + 8) * aligned_bytes_per_pixel;
const u32 aligned_down_start_offset = Common::AlignDown(start_offset, tile_size);
const u32 aligned_start_offset = Common::AlignUp(start_offset, tile_size);
const u32 aligned_end_offset = Common::AlignDown(end_offset, tile_size);
ASSERT(!morton_to_linear ||
(aligned_start_offset == start_offset && aligned_end_offset == end_offset));
// In OpenGL the texture origin is in the bottom left corner as opposed to other
// APIs that have it at the top left. To avoid flipping texture coordinates in
// the shader we read/write the linear buffer from the bottom up
u32 linear_offset = ((height - 8) * width) * aligned_bytes_per_pixel;
u32 tiled_offset = 0;
u32 x = 0;
u32 y = 0;
const auto LinearNextTile = [&] {
x = (x + 8) % width;
linear_offset += 8 * aligned_bytes_per_pixel;
if (!x) {
y = (y + 8) % height;
if (!y) {
return;
}
linear_offset -= width * 9 * aligned_bytes_per_pixel;
}
};
// If during a texture download the start coordinate is not tile aligned, swizzle
// the tile affected to a temporary buffer and copy the part we are interested in
if (start_offset < aligned_start_offset && !morton_to_linear) {
std::array<u8, tile_size> tmp_buf;
auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
MortonCopyTile<morton_to_linear, format, converted>(width, tmp_buf, linear_data);
std::memcpy(tiled_buffer.data(), tmp_buf.data() + start_offset - aligned_down_start_offset,
std::min(aligned_start_offset, end_offset) - start_offset);
tiled_offset += aligned_start_offset - start_offset;
LinearNextTile();
}
// If the copy spans multiple tiles, copy the fully aligned tiles in between.
if (aligned_start_offset < aligned_end_offset) {
const u32 buffer_end = tiled_offset + aligned_end_offset - aligned_start_offset;
while (tiled_offset < buffer_end) {
auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
auto tiled_data = tiled_buffer.subspan(tiled_offset, tile_size);
MortonCopyTile<morton_to_linear, format, converted>(width, tiled_data, linear_data);
tiled_offset += tile_size;
LinearNextTile();
}
}
// If during a texture download the end coordinate is not tile aligned, swizzle
// the tile affected to a temporary buffer and copy the part we are interested in
if (end_offset > std::max(aligned_start_offset, aligned_end_offset) && !morton_to_linear) {
std::array<u8, tile_size> tmp_buf;
auto linear_data = linear_buffer.subspan(linear_offset, linear_tile_stride);
MortonCopyTile<morton_to_linear, format, converted>(width, tmp_buf, linear_data);
std::memcpy(tiled_buffer.data() + tiled_offset, tmp_buf.data(),
end_offset - aligned_end_offset);
}
}
/**
* Performs a linear copy, converting pixel formats if required.
* @tparam decode If true, decodes the texture if needed. Otherwise, encodes if needed.
* @tparam format Pixel format to copy.
* @tparam converted If true, converts the texture to/from the appropriate format.
* @param src_buffer The source pixel data
* @param dst_buffer The destination pixel data
* @return
*/
template <bool decode, PixelFormat format, bool converted = false>
static constexpr void LinearCopy(std::span<u8> src_buffer, std::span<u8> dst_buffer) {
const std::size_t src_size = src_buffer.size();
const std::size_t dst_size = dst_buffer.size();
if constexpr (converted) {
constexpr u32 encoded_bytes_per_pixel = GetFormatBpp(format) / 8;
constexpr u32 decoded_bytes_per_pixel = 4;
constexpr u32 src_bytes_per_pixel =
decode ? encoded_bytes_per_pixel : decoded_bytes_per_pixel;
constexpr u32 dst_bytes_per_pixel =
decode ? decoded_bytes_per_pixel : encoded_bytes_per_pixel;
for (std::size_t src_index = 0, dst_index = 0; src_index < src_size && dst_index < dst_size;
src_index += src_bytes_per_pixel, dst_index += dst_bytes_per_pixel) {
const auto src_pixel = src_buffer.subspan(src_index, src_bytes_per_pixel);
const auto dst_pixel = dst_buffer.subspan(dst_index, dst_bytes_per_pixel);
if constexpr (decode) {
DecodePixel<format, converted>(src_pixel.data(), dst_pixel.data());
} else {
EncodePixel<format, converted>(src_pixel.data(), dst_pixel.data());
}
}
} else {
std::memcpy(dst_buffer.data(), src_buffer.data(), std::min(src_size, dst_size));
}
}
using MortonFunc = void (*)(u32, u32, u32, u32, std::span<u8>, std::span<u8>);
static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE = {
MortonCopy<true, PixelFormat::RGBA8>, // 0
MortonCopy<true, PixelFormat::RGB8>, // 1
MortonCopy<true, PixelFormat::RGB5A1>, // 2
MortonCopy<true, PixelFormat::RGB565>, // 3
MortonCopy<true, PixelFormat::RGBA4>, // 4
MortonCopy<true, PixelFormat::IA8>, // 5
MortonCopy<true, PixelFormat::RG8>, // 6
MortonCopy<true, PixelFormat::I8>, // 7
MortonCopy<true, PixelFormat::A8>, // 8
MortonCopy<true, PixelFormat::IA4>, // 9
MortonCopy<true, PixelFormat::I4>, // 10
MortonCopy<true, PixelFormat::A4>, // 11
MortonCopy<true, PixelFormat::ETC1>, // 12
MortonCopy<true, PixelFormat::ETC1A4>, // 13
MortonCopy<true, PixelFormat::D16>, // 14
nullptr, // 15
MortonCopy<true, PixelFormat::D24>, // 16
MortonCopy<true, PixelFormat::D24S8>, // 17
};
static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE_CONVERTED = {
MortonCopy<true, PixelFormat::RGBA8, true>, // 0
MortonCopy<true, PixelFormat::RGB8, true>, // 1
MortonCopy<true, PixelFormat::RGB5A1, true>, // 2
MortonCopy<true, PixelFormat::RGB565, true>, // 3
MortonCopy<true, PixelFormat::RGBA4, true>, // 4
// The following formats are implicitly converted to RGBA regardless, so ignore them.
nullptr, // 5
nullptr, // 6
nullptr, // 7
nullptr, // 8
nullptr, // 9
nullptr, // 10
nullptr, // 11
nullptr, // 12
nullptr, // 13
MortonCopy<true, PixelFormat::D16, true>, // 14
nullptr, // 15
MortonCopy<true, PixelFormat::D24, true>, // 16
// No conversion here as we need to do a special deinterleaving conversion elsewhere.
nullptr, // 17
};
static constexpr std::array<MortonFunc, 18> SWIZZLE_TABLE = {
MortonCopy<false, PixelFormat::RGBA8>, // 0
MortonCopy<false, PixelFormat::RGB8>, // 1
MortonCopy<false, PixelFormat::RGB5A1>, // 2
MortonCopy<false, PixelFormat::RGB565>, // 3
MortonCopy<false, PixelFormat::RGBA4>, // 4
MortonCopy<false, PixelFormat::IA8>, // 5
MortonCopy<false, PixelFormat::RG8>, // 6
MortonCopy<false, PixelFormat::I8>, // 7
MortonCopy<false, PixelFormat::A8>, // 8
MortonCopy<false, PixelFormat::IA4>, // 9
MortonCopy<false, PixelFormat::I4>, // 10
MortonCopy<false, PixelFormat::A4>, // 11
nullptr, // 12
nullptr, // 13
MortonCopy<false, PixelFormat::D16>, // 14
nullptr, // 15
MortonCopy<false, PixelFormat::D24>, // 16
MortonCopy<false, PixelFormat::D24S8>, // 17
};
static constexpr std::array<MortonFunc, 18> SWIZZLE_TABLE_CONVERTED = {
MortonCopy<false, PixelFormat::RGBA8, true>, // 0
MortonCopy<false, PixelFormat::RGB8, true>, // 1
MortonCopy<false, PixelFormat::RGB5A1, true>, // 2
MortonCopy<false, PixelFormat::RGB565, true>, // 3
MortonCopy<false, PixelFormat::RGBA4, true>, // 4
// The following formats are implicitly converted from RGBA regardless, so ignore them.
nullptr, // 5
nullptr, // 6
nullptr, // 7
nullptr, // 8
nullptr, // 9
nullptr, // 10
nullptr, // 11
nullptr, // 12
nullptr, // 13
MortonCopy<false, PixelFormat::D16, true>, // 14
nullptr, // 15
MortonCopy<false, PixelFormat::D24, true>, // 16
// No conversion here as we need to do a special interleaving conversion elsewhere.
nullptr, // 17
};
using LinearFunc = void (*)(std::span<u8>, std::span<u8>);
static constexpr std::array<LinearFunc, 18> LINEAR_DECODE_TABLE = {
LinearCopy<true, PixelFormat::RGBA8>, // 0
LinearCopy<true, PixelFormat::RGB8>, // 1
LinearCopy<true, PixelFormat::RGB5A1>, // 2
LinearCopy<true, PixelFormat::RGB565>, // 3
LinearCopy<true, PixelFormat::RGBA4>, // 4
// These formats cannot be used linearly and can be ignored.
nullptr, // 5
nullptr, // 6
nullptr, // 7
nullptr, // 8
nullptr, // 9
nullptr, // 10
nullptr, // 11
nullptr, // 12
nullptr, // 13
LinearCopy<true, PixelFormat::D16>, // 14
nullptr, // 15
LinearCopy<true, PixelFormat::D24>, // 16
LinearCopy<true, PixelFormat::D24S8>, // 17
};
static constexpr std::array<LinearFunc, 18> LINEAR_DECODE_TABLE_CONVERTED = {
LinearCopy<true, PixelFormat::RGBA8, true>, // 0
LinearCopy<true, PixelFormat::RGB8, true>, // 1
LinearCopy<true, PixelFormat::RGB5A1, true>, // 2
LinearCopy<true, PixelFormat::RGB565, true>, // 3
LinearCopy<true, PixelFormat::RGBA4, true>, // 4
// These formats cannot be used linearly and can be ignored.
nullptr, // 5
nullptr, // 6
nullptr, // 7
nullptr, // 8
nullptr, // 9
nullptr, // 10
nullptr, // 11
nullptr, // 12
nullptr, // 13
LinearCopy<true, PixelFormat::D16, true>, // 14
nullptr, // 15
LinearCopy<true, PixelFormat::D24, true>, // 16
// No conversion here as we need to do a special deinterleaving conversion elsewhere.
nullptr, // 17
};
static constexpr std::array<LinearFunc, 18> LINEAR_ENCODE_TABLE = {
LinearCopy<false, PixelFormat::RGBA8>, // 0
LinearCopy<false, PixelFormat::RGB8>, // 1
LinearCopy<false, PixelFormat::RGB5A1>, // 2
LinearCopy<false, PixelFormat::RGB565>, // 3
LinearCopy<false, PixelFormat::RGBA4>, // 4
// These formats cannot be used linearly and can be ignored.
nullptr, // 5
nullptr, // 6
nullptr, // 7
nullptr, // 8
nullptr, // 9
nullptr, // 10
nullptr, // 11
nullptr, // 12
nullptr, // 13
LinearCopy<false, PixelFormat::D16>, // 14
nullptr, // 15
LinearCopy<false, PixelFormat::D24>, // 16
LinearCopy<false, PixelFormat::D24S8>, // 17
};
static constexpr std::array<LinearFunc, 18> LINEAR_ENCODE_TABLE_CONVERTED = {
LinearCopy<false, PixelFormat::RGBA8, true>, // 0
LinearCopy<false, PixelFormat::RGB8, true>, // 1
LinearCopy<false, PixelFormat::RGB5A1, true>, // 2
LinearCopy<false, PixelFormat::RGB565, true>, // 3
LinearCopy<false, PixelFormat::RGBA4, true>, // 4
// These formats cannot be used linearly and can be ignored.
nullptr, // 5
nullptr, // 6
nullptr, // 7
nullptr, // 8
nullptr, // 9
nullptr, // 10
nullptr, // 11
nullptr, // 12
nullptr, // 13
LinearCopy<false, PixelFormat::D16, true>, // 14
nullptr, // 15
LinearCopy<false, PixelFormat::D24, true>, // 16
// No conversion here as we need to do a special interleaving conversion elsewhere.
nullptr, // 17
};
} // namespace VideoCore

View file

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

View file

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

View file

@ -0,0 +1,76 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/rasterizer_cache/surface_params.h"
#include "video_core/rasterizer_cache/texture_codec.h"
#include "video_core/rasterizer_cache/utils.h"
namespace VideoCore {
u32 MipLevels(u32 width, u32 height, u32 max_level) {
u32 levels = 1;
while (width > 8 && height > 8) {
levels++;
width >>= 1;
height >>= 1;
}
return std::min(levels, max_level + 1);
}
void EncodeTexture(const SurfaceParams& surface_info, PAddr start_addr, PAddr end_addr,
std::span<u8> source, std::span<u8> dest, bool convert) {
const PixelFormat format = surface_info.pixel_format;
const u32 func_index = static_cast<u32>(format);
if (surface_info.is_tiled) {
const MortonFunc SwizzleImpl =
(convert ? SWIZZLE_TABLE_CONVERTED : SWIZZLE_TABLE)[func_index];
if (SwizzleImpl) {
SwizzleImpl(surface_info.width, surface_info.height, start_addr - surface_info.addr,
end_addr - surface_info.addr, source, dest);
return;
}
} else {
const LinearFunc LinearEncodeImpl =
(convert ? LINEAR_ENCODE_TABLE_CONVERTED : LINEAR_ENCODE_TABLE)[func_index];
if (LinearEncodeImpl) {
LinearEncodeImpl(source, dest);
return;
}
}
LOG_ERROR(HW_GPU, "Unimplemented texture encode function for pixel format = {}, tiled = {}",
func_index, surface_info.is_tiled);
UNIMPLEMENTED();
}
void DecodeTexture(const SurfaceParams& surface_info, PAddr start_addr, PAddr end_addr,
std::span<u8> source, std::span<u8> dest, bool convert) {
const PixelFormat format = surface_info.pixel_format;
const u32 func_index = static_cast<u32>(format);
if (surface_info.is_tiled) {
const MortonFunc UnswizzleImpl =
(convert ? UNSWIZZLE_TABLE_CONVERTED : UNSWIZZLE_TABLE)[func_index];
if (UnswizzleImpl) {
UnswizzleImpl(surface_info.width, surface_info.height, start_addr - surface_info.addr,
end_addr - surface_info.addr, dest, source);
return;
}
} else {
const LinearFunc LinearDecodeImpl =
(convert ? LINEAR_DECODE_TABLE_CONVERTED : LINEAR_DECODE_TABLE)[func_index];
if (LinearDecodeImpl) {
LinearDecodeImpl(source, dest);
return;
}
}
LOG_ERROR(HW_GPU, "Unimplemented texture decode function for pixel format = {}, tiled = {}",
func_index, surface_info.is_tiled);
UNIMPLEMENTED();
}
} // namespace VideoCore

View file

@ -0,0 +1,142 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <compare>
#include <span>
#include <boost/icl/right_open_interval.hpp>
#include "common/hash.h"
#include "common/math_util.h"
#include "common/vector_math.h"
#include "video_core/rasterizer_cache/pixel_format.h"
namespace VideoCore {
using SurfaceInterval = boost::icl::right_open_interval<PAddr>;
struct Offset {
constexpr auto operator<=>(const Offset&) const noexcept = default;
u32 x = 0;
u32 y = 0;
};
struct Extent {
constexpr auto operator<=>(const Extent&) const noexcept = default;
u32 width = 1;
u32 height = 1;
};
union ClearValue {
Common::Vec4f color;
struct {
float depth;
u8 stencil;
};
};
struct TextureClear {
u32 texture_level;
Common::Rectangle<u32> texture_rect;
ClearValue value;
};
struct TextureCopy {
u32 src_level;
u32 dst_level;
u32 src_layer;
u32 dst_layer;
Offset src_offset;
Offset dst_offset;
Extent extent;
};
struct TextureBlit {
u32 src_level;
u32 dst_level;
u32 src_layer;
u32 dst_layer;
Common::Rectangle<u32> src_rect;
Common::Rectangle<u32> dst_rect;
};
struct BufferTextureCopy {
u32 buffer_offset;
u32 buffer_size;
Common::Rectangle<u32> texture_rect;
u32 texture_level;
};
struct StagingData {
u32 size = 0;
std::span<u8> mapped{};
u64 buffer_offset = 0;
};
struct TextureCubeConfig {
PAddr px;
PAddr nx;
PAddr py;
PAddr ny;
PAddr pz;
PAddr nz;
u32 width;
u32 levels;
Pica::TexturingRegs::TextureFormat format;
bool operator==(const TextureCubeConfig& rhs) const {
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) == 0;
}
bool operator!=(const TextureCubeConfig& rhs) const {
return std::memcmp(this, &rhs, sizeof(TextureCubeConfig)) != 0;
}
const u64 Hash() const {
return Common::ComputeHash64(this, sizeof(TextureCubeConfig));
}
};
class SurfaceParams;
u32 MipLevels(u32 width, u32 height, u32 max_level);
/**
* Encodes a linear texture to the expected linear or tiled format.
*
* @param surface_info Structure used to query the surface information.
* @param start_addr The start address of the dest data. Used if tiled.
* @param end_addr The end address of the dest data. Used if tiled.
* @param source_tiled The source linear texture data.
* @param dest_linear The output buffer where the encoded linear or tiled data will be written to.
* @param convert Whether the pixel format needs to be converted.
*/
void EncodeTexture(const SurfaceParams& surface_info, PAddr start_addr, PAddr end_addr,
std::span<u8> source, std::span<u8> dest, bool convert = false);
/**
* Decodes a linear or tiled texture to the expected linear format.
*
* @param surface_info Structure used to query the surface information.
* @param start_addr The start address of the source data. Used if tiled.
* @param end_addr The end address of the source data. Used if tiled.
* @param source_tiled The source linear or tiled texture data.
* @param dest_linear The output buffer where the decoded linear data will be written to.
* @param convert Whether the pixel format needs to be converted.
*/
void DecodeTexture(const SurfaceParams& surface_info, PAddr start_addr, PAddr end_addr,
std::span<u8> source, std::span<u8> dest, bool convert = false);
} // namespace VideoCore
namespace std {
template <>
struct hash<VideoCore::TextureCubeConfig> {
std::size_t operator()(const VideoCore::TextureCubeConfig& config) const noexcept {
return config.Hash();
}
};
} // namespace std

View file

@ -281,6 +281,14 @@ struct FramebufferRegs {
return 0;
}
[[nodiscard]] bool IsShadowRendering() const {
return output_merger.fragment_operation_mode == FragmentOperationMode::Shadow;
}
[[nodiscard]] bool HasStencil() const {
return framebuffer.depth_format == DepthFormat::D24S8;
};
INSERT_PADDING_WORDS(0x10); // Gas related registers
union {

View file

@ -6,8 +6,8 @@
#include <array>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/math_util.h"
#include "common/vector_math.h"
#include "video_core/pica_types.h"
namespace Pica {
@ -41,6 +41,18 @@ struct RasterizerRegs {
float24::FromRaw(clip_coef[2]), float24::FromRaw(clip_coef[3])};
}
Common::Rectangle<s32> GetViewportRect() const {
return {
// These registers hold half-width and half-height, so must be multiplied by 2
viewport_corner.x, // left
viewport_corner.y + // top
static_cast<s32>(float24::FromRaw(viewport_size_y).ToFloat32() * 2),
viewport_corner.x + // right
static_cast<s32>(float24::FromRaw(viewport_size_x).ToFloat32() * 2),
viewport_corner.y // bottom
};
}
INSERT_PADDING_WORDS(0x1);
BitField<0, 24, u32> viewport_depth_range; // float24

View file

@ -99,29 +99,25 @@ struct TexturingRegs {
ETC1A4 = 13, // compressed
};
static unsigned NibblesPerPixel(TextureFormat format) {
static u32 NibblesPerPixel(TextureFormat format) {
switch (format) {
case TextureFormat::RGBA8:
return 8;
case TextureFormat::RGB8:
return 6;
case TextureFormat::RGB5A1:
case TextureFormat::RGB565:
case TextureFormat::RGBA4:
case TextureFormat::IA8:
case TextureFormat::RG8:
return 4;
case TextureFormat::I4:
case TextureFormat::A4:
return 1;
case TextureFormat::I8:
case TextureFormat::A8:
case TextureFormat::IA4:
return 2;
default: // placeholder for yet unknown formats
UNIMPLEMENTED();
return 0;

View file

@ -1,10 +0,0 @@
//? #version 320 es
out highp uint color;
uniform highp sampler2D depth;
uniform int lod;
void main() {
color = uint(texelFetch(depth, ivec2(gl_FragCoord.xy), lod).x * (exp2(32.0) - 1.0));
}

View file

@ -1,8 +0,0 @@
//? #version 320 es
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
}

View file

@ -1,9 +0,0 @@
//? #version 320 es
#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : enable
out highp uint color;
void main() {
color = uint(gl_LastFragDepthARM * (exp2(24.0) - 1.0)) << 8;
color |= uint(gl_LastFragStencilARM);
}

View file

@ -0,0 +1,218 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/settings.h"
#include "video_core/rasterizer_cache/pixel_format.h"
#include "video_core/renderer_opengl/gl_blit_helper.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_texture_runtime.h"
#include "video_core/host_shaders/full_screen_triangle_vert.h"
#include "video_core/host_shaders/texture_filtering/bicubic_frag.h"
#include "video_core/host_shaders/texture_filtering/nearest_neighbor_frag.h"
#include "video_core/host_shaders/texture_filtering/refine_frag.h"
#include "video_core/host_shaders/texture_filtering/scale_force_frag.h"
#include "video_core/host_shaders/texture_filtering/tex_coord_vert.h"
#include "video_core/host_shaders/texture_filtering/x_gradient_frag.h"
#include "video_core/host_shaders/texture_filtering/xbrz_freescale_frag.h"
#include "video_core/host_shaders/texture_filtering/y_gradient_frag.h"
namespace OpenGL {
using Settings::TextureFilter;
using VideoCore::SurfaceType;
namespace {
struct TempTexture {
OGLTexture tex;
OGLFramebuffer fbo;
};
OGLSampler CreateSampler(GLenum filter) {
OGLSampler sampler;
sampler.Create();
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, filter);
glSamplerParameteri(sampler.handle, GL_TEXTURE_MAG_FILTER, filter);
glSamplerParameteri(sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return sampler;
}
OGLProgram CreateProgram(std::string_view frag) {
OGLProgram program;
program.Create(HostShaders::FULL_SCREEN_TRIANGLE_VERT, frag);
glProgramUniform2f(program.handle, 0, 1.f, 1.f);
glProgramUniform2f(program.handle, 1, 0.f, 0.f);
return program;
}
} // Anonymous namespace
BlitHelper::BlitHelper(TextureRuntime& runtime_)
: runtime{runtime_}, linear_sampler{CreateSampler(GL_LINEAR)},
nearest_sampler{CreateSampler(GL_NEAREST)}, bicubic_program{CreateProgram(
HostShaders::BICUBIC_FRAG)},
nearest_program{CreateProgram(HostShaders::NEAREST_NEIGHBOR_FRAG)},
scale_force_program{CreateProgram(HostShaders::SCALE_FORCE_FRAG)},
xbrz_program{CreateProgram(HostShaders::XBRZ_FREESCALE_FRAG)},
gradient_x_program{CreateProgram(HostShaders::X_GRADIENT_FRAG)},
gradient_y_program{CreateProgram(HostShaders::Y_GRADIENT_FRAG)},
refine_program{CreateProgram(HostShaders::REFINE_FRAG)} {
vao.Create();
filter_fbo.Create();
state.draw.vertex_array = vao.handle;
for (u32 i = 0; i < 3; i++) {
state.texture_units[i].sampler = i == 2 ? nearest_sampler.handle : linear_sampler.handle;
}
}
BlitHelper::~BlitHelper() = default;
bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) {
// Filtering to depth stencil surfaces isn't supported.
if (surface.type == SurfaceType::Depth || surface.type == SurfaceType::DepthStencil) {
return false;
}
// Avoid filtering for mipmaps as the result often looks terrible.
if (blit.src_level != 0) {
return true;
}
const OpenGLState prev_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = surface.Handle(false);
const auto filter{Settings::values.texture_filter.GetValue()};
switch (filter) {
case TextureFilter::None:
break;
case TextureFilter::Anime4K:
FilterAnime4K(surface, blit);
break;
case TextureFilter::Bicubic:
FilterBicubic(surface, blit);
break;
case TextureFilter::NearestNeighbor:
FilterNearest(surface, blit);
break;
case TextureFilter::ScaleForce:
FilterScaleForce(surface, blit);
break;
case TextureFilter::xBRZ:
FilterXbrz(surface, blit);
break;
}
prev_state.Apply();
return true;
}
void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit) {
static constexpr u8 internal_scale_factor = 2;
const auto& tuple = surface.Tuple();
const u32 src_width = blit.src_rect.GetWidth();
const u32 src_height = blit.src_rect.GetHeight();
const auto temp_rect{blit.src_rect * internal_scale_factor};
const auto setup_temp_tex = [&](GLint internal_format, GLint format, u32 width, u32 height) {
TempTexture texture;
texture.fbo.Create();
texture.tex.Create();
state.texture_units[1].texture_2d = texture.tex.handle;
state.draw.draw_framebuffer = texture.fbo.handle;
state.Apply();
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture.tex.handle);
glTexStorage2D(GL_TEXTURE_2D, 1, internal_format, width, height);
return texture;
};
// Create intermediate textures
auto SRC = setup_temp_tex(tuple.internal_format, tuple.format, src_width, src_height);
auto XY = setup_temp_tex(GL_RG16F, GL_RG, temp_rect.GetWidth(), temp_rect.GetHeight());
auto LUMAD = setup_temp_tex(GL_R16F, GL_RED, temp_rect.GetWidth(), temp_rect.GetHeight());
// Copy to SRC
glCopyImageSubData(surface.Handle(false), GL_TEXTURE_2D, 0, blit.src_rect.left,
blit.src_rect.bottom, 0, SRC.tex.handle, GL_TEXTURE_2D, 0, 0, 0, 0,
src_width, src_height, 1);
state.texture_units[0].texture_2d = SRC.tex.handle;
state.texture_units[1].texture_2d = LUMAD.tex.handle;
state.texture_units[2].texture_2d = XY.tex.handle;
// gradient x pass
Draw(gradient_x_program, XY.tex.handle, XY.fbo.handle, 0, temp_rect);
// gradient y pass
Draw(gradient_y_program, LUMAD.tex.handle, LUMAD.fbo.handle, 0, temp_rect);
// refine pass
Draw(refine_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
// These will have handles from the previous texture that was filtered, reset them to avoid
// binding invalid textures.
state.texture_units[0].texture_2d = 0;
state.texture_units[1].texture_2d = 0;
state.texture_units[2].texture_2d = 0;
state.Apply();
}
void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) {
SetParams(bicubic_program, surface.width, surface.height, blit.src_rect);
Draw(bicubic_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit) {
SetParams(nearest_program, surface.width, surface.height, blit.src_rect);
Draw(nearest_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) {
SetParams(scale_force_program, surface.width, surface.height, blit.src_rect);
Draw(scale_force_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) {
glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale));
SetParams(xbrz_program, surface.width, surface.height, blit.src_rect);
Draw(xbrz_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::SetParams(OGLProgram& program, u32 src_width, u32 src_height,
Common::Rectangle<u32> src_rect) {
glProgramUniform2f(
program.handle, 0,
static_cast<float>(src_rect.right - src_rect.left) / static_cast<float>(src_width),
static_cast<float>(src_rect.top - src_rect.bottom) / static_cast<float>(src_height));
glProgramUniform2f(program.handle, 1,
static_cast<float>(src_rect.left) / static_cast<float>(src_width),
static_cast<float>(src_rect.bottom) / static_cast<float>(src_height));
}
void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level,
Common::Rectangle<u32> dst_rect) {
state.draw.draw_framebuffer = dst_fbo;
state.draw.shader_program = program.handle;
state.scissor.enabled = true;
state.scissor.x = dst_rect.left;
state.scissor.y = dst_rect.bottom;
state.scissor.width = dst_rect.GetWidth();
state.scissor.height = dst_rect.GetHeight();
state.viewport.x = dst_rect.left;
state.viewport.y = dst_rect.bottom;
state.viewport.width = dst_rect.GetWidth();
state.viewport.height = dst_rect.GetHeight();
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex,
dst_level);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
} // namespace OpenGL

View file

@ -0,0 +1,61 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/math_util.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
namespace VideoCore {
struct TextureBlit;
}
namespace OpenGL {
class TextureRuntime;
class Surface;
class BlitHelper {
public:
BlitHelper(TextureRuntime& runtime);
~BlitHelper();
bool Filter(Surface& surface, const VideoCore::TextureBlit& blit);
private:
void FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit);
void FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit);
void SetParams(OGLProgram& program, u32 src_width, u32 src_height,
Common::Rectangle<u32> src_rect);
void Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level,
Common::Rectangle<u32> dst_rect);
private:
TextureRuntime& runtime;
OGLVertexArray vao;
OpenGLState state;
OGLFramebuffer filter_fbo;
OGLSampler linear_sampler;
OGLSampler nearest_sampler;
OGLProgram bicubic_program;
OGLProgram nearest_program;
OGLProgram scale_force_program;
OGLProgram xbrz_program;
OGLProgram gradient_x_program;
OGLProgram gradient_y_program;
OGLProgram refine_program;
};
} // namespace OpenGL

View file

@ -71,10 +71,11 @@ static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum
id, message);
}
Driver::Driver(Core::TelemetrySession& telemetry_session_) : telemetry_session{telemetry_session_} {
Driver::Driver(Core::TelemetrySession& telemetry_session_)
: telemetry_session{telemetry_session_}, is_gles{Settings::values.use_gles.GetValue()} {
const bool enable_debug = Settings::values.renderer_debug.GetValue();
if (enable_debug) {
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(DebugHandler, nullptr);
}
@ -90,6 +91,18 @@ bool Driver::HasBug(DriverBug bug) const {
return True(bugs & bug);
}
bool Driver::HasDebugTool() {
GLint num_extensions;
glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
for (GLuint index = 0; index < static_cast<GLuint>(num_extensions); ++index) {
const auto name = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, index));
if (!std::strcmp(name, "GL_EXT_debug_tool")) {
return true;
}
}
return false;
}
void Driver::ReportDriverInfo() {
// Report the context version and the vendor string
gl_version = std::string_view{reinterpret_cast<const char*>(glGetString(GL_VERSION))};

View file

@ -48,6 +48,9 @@ public:
/// Returns true of the driver has a particular bug stated in the DriverBug enum
bool HasBug(DriverBug bug) const;
/// Returns true if any debug tool is attached
bool HasDebugTool();
/// Returns the vendor of the currently selected physical device
Vendor GetVendor() const {
return vendor;
@ -58,6 +61,11 @@ public:
return gpu_vendor;
}
/// Returns true if the an OpenGLES context is used
bool IsOpenGLES() const noexcept {
return is_gles;
}
/// Returns true if the implementation is suitable for emulation
bool IsSuitable() const {
return is_suitable;
@ -99,6 +107,7 @@ private:
Vendor vendor = Vendor::Unknown;
DriverBug bugs{};
bool is_suitable{};
bool is_gles{};
bool ext_buffer_storage{};
bool arb_buffer_storage{};

View file

@ -5,72 +5,29 @@
#include "common/scope_exit.h"
#include "video_core/renderer_opengl/gl_format_reinterpreter.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_texture_runtime.h"
#include "video_core/host_shaders/format_reinterpreter/d24s8_to_rgba8_frag.h"
#include "video_core/host_shaders/format_reinterpreter/fullscreen_quad_vert.h"
#include "video_core/host_shaders/format_reinterpreter/rgba4_to_rgb5a1_frag.h"
namespace OpenGL {
class RGBA4toRGB5A1 final : public FormatReinterpreterBase {
public:
RGBA4toRGB5A1() {
constexpr std::string_view vs_source = R"(
out vec2 dst_coord;
uniform mediump ivec2 dst_size;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size);
}
)";
constexpr std::string_view fs_source = R"(
in mediump vec2 dst_coord;
out lowp vec4 frag_color;
uniform lowp sampler2D source;
uniform mediump ivec2 dst_size;
uniform mediump ivec2 src_size;
uniform mediump ivec2 src_offset;
void main() {
mediump ivec2 tex_coord;
if (src_size == dst_size) {
tex_coord = ivec2(dst_coord);
} else {
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
mediump int y = tex_index / src_size.x;
tex_coord = ivec2(tex_index - y * src_size.x, y);
}
tex_coord -= src_offset;
lowp ivec4 rgba4 = ivec4(texelFetch(source, tex_coord, 0) * (exp2(4.0) - 1.0));
lowp ivec3 rgb5 =
((rgba4.rgb << ivec3(1, 2, 3)) | (rgba4.gba >> ivec3(3, 2, 1))) & 0x1F;
frag_color = vec4(vec3(rgb5) / (exp2(5.0) - 1.0), rgba4.a & 0x01);
}
)";
program.Create(vs_source.data(), fs_source.data());
RGBA4toRGB5A1::RGBA4toRGB5A1() {
program.Create(HostShaders::FULLSCREEN_QUAD_VERT, HostShaders::RGBA4_TO_RGB5A1_FRAG);
dst_size_loc = glGetUniformLocation(program.handle, "dst_size");
src_size_loc = glGetUniformLocation(program.handle, "src_size");
src_offset_loc = glGetUniformLocation(program.handle, "src_offset");
vao.Create();
}
}
PixelFormat GetSourceFormat() const override {
return PixelFormat::RGBA4;
}
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override {
void RGBA4toRGB5A1::Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest,
Common::Rectangle<u32> dst_rect) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = src_tex.handle;
state.texture_units[0].texture_2d = source.Handle();
state.draw.draw_framebuffer = draw_fbo.handle;
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
@ -79,72 +36,18 @@ void main() {
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dest.Handle(),
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight());
glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight());
glUniform2i(src_offset_loc, src_rect.left, src_rect.bottom);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
private:
OGLProgram program;
GLint dst_size_loc{-1}, src_size_loc{-1}, src_offset_loc{-1};
OGLVertexArray vao;
};
class ShaderD24S8toRGBA8 final : public FormatReinterpreterBase {
public:
ShaderD24S8toRGBA8() {
constexpr std::string_view vs_source = R"(
out vec2 dst_coord;
uniform mediump ivec2 dst_size;
const vec2 vertices[4] =
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
void main() {
gl_Position = vec4(vertices[gl_VertexID], 0.0, 1.0);
dst_coord = (vertices[gl_VertexID] / 2.0 + 0.5) * vec2(dst_size);
}
)";
constexpr std::string_view fs_source = R"(
in mediump vec2 dst_coord;
out lowp vec4 frag_color;
uniform highp sampler2D depth;
uniform lowp usampler2D stencil;
uniform mediump ivec2 dst_size;
uniform mediump ivec2 src_size;
uniform mediump ivec2 src_offset;
void main() {
mediump ivec2 tex_coord;
if (src_size == dst_size) {
tex_coord = ivec2(dst_coord);
} else {
highp int tex_index = int(dst_coord.y) * dst_size.x + int(dst_coord.x);
mediump int y = tex_index / src_size.x;
tex_coord = ivec2(tex_index - y * src_size.x, y);
}
tex_coord -= src_offset;
highp uint depth_val =
uint(texelFetch(depth, tex_coord, 0).x * (exp2(32.0) - 1.0));
lowp uint stencil_val = texelFetch(stencil, tex_coord, 0).x;
highp uvec4 components =
uvec4(stencil_val, (uvec3(depth_val) >> uvec3(24u, 16u, 8u)) & 0x000000FFu);
frag_color = vec4(components) / (exp2(8.0) - 1.0);
}
)";
program.Create(vs_source.data(), fs_source.data());
ShaderD24S8toRGBA8::ShaderD24S8toRGBA8() {
program.Create(HostShaders::FULLSCREEN_QUAD_VERT, HostShaders::D24S8_TO_RGBA8_FRAG);
dst_size_loc = glGetUniformLocation(program.handle, "dst_size");
src_size_loc = glGetUniformLocation(program.handle, "src_size");
src_offset_loc = glGetUniformLocation(program.handle, "src_offset");
@ -168,25 +71,21 @@ void main() {
"Texture views are unsupported, reinterpretation will do intermediate copy");
temp_tex.Create();
}
}
}
PixelFormat GetSourceFormat() const override {
return PixelFormat::D24S8;
}
void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override {
void ShaderD24S8toRGBA8::Reinterpret(Surface& source, Common::Rectangle<u32> src_rect,
Surface& dest, Common::Rectangle<u32> dst_rect) {
OpenGLState prev_state = OpenGLState::GetCurState();
SCOPE_EXIT({ prev_state.Apply(); });
OpenGLState state;
state.texture_units[0].texture_2d = src_tex.handle;
state.texture_units[0].texture_2d = source.Handle();
if (use_texture_view) {
temp_tex.Create();
glActiveTexture(GL_TEXTURE1);
glTextureView(temp_tex.handle, GL_TEXTURE_2D, src_tex.handle, GL_DEPTH24_STENCIL8, 0, 1,
0, 1);
glTextureView(temp_tex.handle, GL_TEXTURE_2D, source.Handle(), GL_DEPTH24_STENCIL8, 0, 1, 0,
1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
} else if (src_rect.top > temp_rect.top || src_rect.right > temp_rect.right) {
@ -212,54 +111,22 @@ void main() {
glActiveTexture(GL_TEXTURE1);
if (!use_texture_view) {
glCopyImageSubData(src_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
glCopyImageSubData(source.Handle(), GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
temp_tex.handle, GL_TEXTURE_2D, 0, src_rect.left, src_rect.bottom, 0,
src_rect.GetWidth(), src_rect.GetHeight(), 1);
}
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
dst_tex.handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dest.Handle(),
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glUniform2i(dst_size_loc, dst_rect.GetWidth(), dst_rect.GetHeight());
glUniform2i(src_size_loc, src_rect.GetWidth(), src_rect.GetHeight());
glUniform2i(src_offset_loc, src_rect.left, src_rect.bottom);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (use_texture_view) {
temp_tex.Release();
}
}
private:
bool use_texture_view{};
OGLProgram program{};
GLint dst_size_loc{-1}, src_size_loc{-1}, src_offset_loc{-1};
OGLVertexArray vao{};
OGLTexture temp_tex{};
Common::Rectangle<u32> temp_rect{0, 0, 0, 0};
};
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) {
const u32 dst_index = static_cast<u32>(dest);
return reinterpreters[dst_index].push_back(std::move(obj));
};
Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
LOG_INFO(Render_OpenGL, "Using shader for D24S8 to RGBA8 reinterpretation");
Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
}
auto FormatReinterpreterOpenGL::GetPossibleReinterpretations(PixelFormat dst_format)
-> const ReinterpreterList& {
return reinterpreters[static_cast<u32>(dst_format)];
}
} // namespace OpenGL

View file

@ -11,7 +11,7 @@
namespace OpenGL {
class RasterizerCacheOpenGL;
class Surface;
class FormatReinterpreterBase {
public:
@ -22,9 +22,9 @@ public:
virtual ~FormatReinterpreterBase() = default;
virtual PixelFormat GetSourceFormat() const = 0;
virtual void Reinterpret(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) = 0;
virtual VideoCore::PixelFormat GetSourceFormat() const = 0;
virtual void Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest,
Common::Rectangle<u32> dst_rect) = 0;
protected:
OGLFramebuffer read_fbo;
@ -33,15 +33,45 @@ protected:
using ReinterpreterList = std::vector<std::unique_ptr<FormatReinterpreterBase>>;
class FormatReinterpreterOpenGL : NonCopyable {
class RGBA4toRGB5A1 final : public FormatReinterpreterBase {
public:
FormatReinterpreterOpenGL();
~FormatReinterpreterOpenGL() = default;
RGBA4toRGB5A1();
const ReinterpreterList& GetPossibleReinterpretations(PixelFormat dst_format);
VideoCore::PixelFormat GetSourceFormat() const override {
return VideoCore::PixelFormat::RGBA4;
}
void Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest,
Common::Rectangle<u32> dst_rect) override;
private:
std::array<ReinterpreterList, PIXEL_FORMAT_COUNT> reinterpreters;
OGLProgram program;
GLint dst_size_loc{-1};
GLint src_size_loc{-1};
GLint src_offset_loc{-1};
OGLVertexArray vao;
};
class ShaderD24S8toRGBA8 final : public FormatReinterpreterBase {
public:
ShaderD24S8toRGBA8();
VideoCore::PixelFormat GetSourceFormat() const override {
return VideoCore::PixelFormat::D24S8;
}
void Reinterpret(Surface& source, Common::Rectangle<u32> src_rect, Surface& dest,
Common::Rectangle<u32> dst_rect) override;
private:
bool use_texture_view{};
OGLProgram program{};
GLint dst_size_loc{-1};
GLint src_size_loc{-1};
GLint src_offset_loc{-1};
OGLVertexArray vao{};
OGLTexture temp_tex{};
Common::Rectangle<u32> temp_rect{0, 0, 0, 0};
};
} // namespace OpenGL

View file

@ -25,9 +25,10 @@ MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(255, 128,
MICROPROFILE_DEFINE(OpenGL_VS, "OpenGL", "Vertex Shader Setup", MP_RGB(192, 128, 128));
MICROPROFILE_DEFINE(OpenGL_GS, "OpenGL", "Geometry Shader Setup", MP_RGB(128, 192, 128));
MICROPROFILE_DEFINE(OpenGL_Drawing, "OpenGL", "Drawing", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Blits, "OpenGL", "Blits", MP_RGB(100, 100, 255));
MICROPROFILE_DEFINE(OpenGL_CacheManagement, "OpenGL", "Cache Mgmt", MP_RGB(100, 255, 100));
using VideoCore::SurfaceType;
constexpr std::size_t VERTEX_BUFFER_SIZE = 16 * 1024 * 1024;
constexpr std::size_t INDEX_BUFFER_SIZE = 2 * 1024 * 1024;
constexpr std::size_t UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
@ -62,17 +63,27 @@ GLenum MakeAttributeType(Pica::PipelineRegs::VertexAttributeFormat format) {
return GL_UNSIGNED_BYTE;
}
[[nodiscard]] GLsizeiptr TextureBufferSize() {
// Use the smallest texel size from the texel views
// which corresponds to GL_RG32F
GLint max_texel_buffer_size;
glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &max_texel_buffer_size);
LOG_INFO(Render_OpenGL, "Max texture buffer size: {}", max_texel_buffer_size);
return std::min<GLsizeiptr>(max_texel_buffer_size * 8, TEXTURE_BUFFER_SIZE);
}
} // Anonymous namespace
RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer,
Driver& driver_)
: VideoCore::RasterizerAccelerated{memory}, driver{driver_}, res_cache{renderer},
: VideoCore::RasterizerAccelerated{memory}, driver{driver_}, runtime{driver, renderer},
res_cache{memory, runtime, regs, renderer}, texture_buffer_size{TextureBufferSize()},
vertex_buffer{driver, GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE},
uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE},
index_buffer{driver, GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE},
texture_buffer{driver, GL_TEXTURE_BUFFER, TEXTURE_BUFFER_SIZE}, texture_lf_buffer{
texture_buffer{driver, GL_TEXTURE_BUFFER, texture_buffer_size}, texture_lf_buffer{
driver, GL_TEXTURE_BUFFER,
TEXTURE_BUFFER_SIZE} {
texture_buffer_size} {
// Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
state.clip_distance[0] = true;
@ -372,11 +383,8 @@ void RasterizerOpenGL::DrawTriangles() {
bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
MICROPROFILE_SCOPE(OpenGL_Drawing);
bool shadow_rendering = regs.framebuffer.output_merger.fragment_operation_mode ==
Pica::FramebufferRegs::FragmentOperationMode::Shadow;
const bool has_stencil =
regs.framebuffer.framebuffer.depth_format == Pica::FramebufferRegs::DepthFormat::D24S8;
const bool shadow_rendering = regs.framebuffer.IsShadowRendering();
const bool has_stencil = regs.framebuffer.HasStencil();
const bool write_color_fb = shadow_rendering || state.color_mask.red_enabled == GL_TRUE ||
state.color_mask.green_enabled == GL_TRUE ||
@ -394,108 +402,45 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
(write_depth_fb || regs.framebuffer.output_merger.depth_test_enable != 0 ||
(has_stencil && state.stencil.test_enabled));
Common::Rectangle<s32> viewport_rect_unscaled{
// These registers hold half-width and half-height, so must be multiplied by 2
regs.rasterizer.viewport_corner.x, // left
regs.rasterizer.viewport_corner.y + // top
static_cast<s32>(Pica::float24::FromRaw(regs.rasterizer.viewport_size_y).ToFloat32() *
2),
regs.rasterizer.viewport_corner.x + // right
static_cast<s32>(Pica::float24::FromRaw(regs.rasterizer.viewport_size_x).ToFloat32() *
2),
regs.rasterizer.viewport_corner.y // bottom
};
Surface color_surface;
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);
const u16 res_scale = color_surface != nullptr
? color_surface->res_scale
: (depth_surface == nullptr ? 1u : depth_surface->res_scale);
Common::Rectangle<u32> draw_rect{
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
viewport_rect_unscaled.left * res_scale,
surfaces_rect.left, surfaces_rect.right)), // Left
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
viewport_rect_unscaled.top * res_scale,
surfaces_rect.bottom, surfaces_rect.top)), // Top
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.left) +
viewport_rect_unscaled.right * res_scale,
surfaces_rect.left, surfaces_rect.right)), // Right
static_cast<u32>(std::clamp<s32>(static_cast<s32>(surfaces_rect.bottom) +
viewport_rect_unscaled.bottom * res_scale,
surfaces_rect.bottom, surfaces_rect.top))}; // Bottom
// Bind the framebuffer surfaces
state.draw.draw_framebuffer = framebuffer.handle;
state.Apply();
if (shadow_rendering) {
if (color_surface == nullptr) {
const Framebuffer framebuffer =
res_cache.GetFramebufferSurfaces(using_color_fb, using_depth_fb);
const bool has_color = framebuffer.HasAttachment(SurfaceType::Color);
const bool has_depth_stencil = framebuffer.HasAttachment(SurfaceType::DepthStencil);
if (!has_color && (shadow_rendering || !has_depth_stencil)) {
return true;
}
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
color_surface->width * color_surface->res_scale);
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
color_surface->height * color_surface->res_scale);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
state.image_shadow_buffer = color_surface->texture.handle;
} else {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
color_surface != nullptr ? color_surface->texture.handle : 0, 0);
if (depth_surface != nullptr) {
if (has_stencil) {
// attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, depth_surface->texture.handle, 0);
} else {
// attach depth
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
depth_surface->texture.handle, 0);
// clear stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
}
} else {
// clear both depth and stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
}
// Bind the framebuffer surfaces
if (shadow_rendering) {
state.image_shadow_buffer = framebuffer.Attachment(SurfaceType::Color);
}
state.draw.draw_framebuffer = framebuffer.Handle();
// Sync the viewport
state.viewport.x =
static_cast<GLint>(surfaces_rect.left) + viewport_rect_unscaled.left * res_scale;
state.viewport.y =
static_cast<GLint>(surfaces_rect.bottom) + viewport_rect_unscaled.bottom * res_scale;
state.viewport.width = static_cast<GLsizei>(viewport_rect_unscaled.GetWidth() * res_scale);
state.viewport.height = static_cast<GLsizei>(viewport_rect_unscaled.GetHeight() * res_scale);
const auto viewport = framebuffer.Viewport();
state.viewport.x = viewport.x;
state.viewport.y = viewport.y;
state.viewport.width = viewport.width;
state.viewport.height = viewport.height;
// Viewport can have negative offsets or larger dimensions than our framebuffer sub-rect.
// Enable scissor test to prevent drawing outside of the framebuffer region
const auto draw_rect = framebuffer.DrawRect();
state.scissor.enabled = true;
state.scissor.x = draw_rect.left;
state.scissor.y = draw_rect.bottom;
state.scissor.width = draw_rect.GetWidth();
state.scissor.height = draw_rect.GetHeight();
state.Apply();
const u32 res_scale = framebuffer.ResolutionScale();
if (uniform_block_data.data.framebuffer_scale != res_scale) {
uniform_block_data.data.framebuffer_scale = res_scale;
uniform_block_data.dirty = true;
}
// Scissor checks are window-, not viewport-relative, which means that if the cached texture
// sub-rect changes, the scissor bounds also need to be updated.
GLint scissor_x1 =
static_cast<GLint>(surfaces_rect.left + regs.rasterizer.scissor_test.x1 * res_scale);
GLint scissor_y1 =
static_cast<GLint>(surfaces_rect.bottom + regs.rasterizer.scissor_test.y1 * res_scale);
// x2, y2 have +1 added to cover the entire pixel area, otherwise you might get cracks when
// scaling or doing multisampling.
GLint scissor_x2 =
static_cast<GLint>(surfaces_rect.left + (regs.rasterizer.scissor_test.x2 + 1) * res_scale);
GLint scissor_y2 = static_cast<GLint>(surfaces_rect.bottom +
(regs.rasterizer.scissor_test.y2 + 1) * res_scale);
// Update scissor uniforms
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = framebuffer.Scissor();
if (uniform_block_data.data.scissor_x1 != scissor_x1 ||
uniform_block_data.data.scissor_x2 != scissor_x2 ||
uniform_block_data.data.scissor_y1 != scissor_y1 ||
@ -508,127 +453,8 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
uniform_block_data.dirty = true;
}
bool need_duplicate_texture = false;
auto CheckBarrier = [&need_duplicate_texture, &color_surface](GLuint handle) {
if (color_surface && color_surface->texture.handle == handle) {
need_duplicate_texture = true;
}
};
const auto BindCubeFace = [&](GLuint& target, Pica::TexturingRegs::CubeFace face,
Pica::Texture::TextureInfo& info) {
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
Surface surface = res_cache.GetTextureSurface(info);
if (surface != nullptr) {
CheckBarrier(target = surface->texture.handle);
} else {
target = 0;
}
};
// Sync and bind the texture surfaces
const auto pica_textures = regs.texturing.GetTextures();
for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
const auto& texture = pica_textures[texture_index];
if (texture.enabled) {
if (texture_index == 0) {
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
switch (texture.config.type.Value()) {
case TextureType::Shadow2D: {
Surface surface = res_cache.GetTextureSurface(texture);
if (surface != nullptr) {
CheckBarrier(state.image_shadow_texture_px = surface->texture.handle);
} else {
state.image_shadow_texture_px = 0;
}
continue;
}
case TextureType::ShadowCube: {
using CubeFace = Pica::TexturingRegs::CubeFace;
auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config,
texture.format);
BindCubeFace(state.image_shadow_texture_px, CubeFace::PositiveX, info);
BindCubeFace(state.image_shadow_texture_nx, CubeFace::NegativeX, info);
BindCubeFace(state.image_shadow_texture_py, CubeFace::PositiveY, info);
BindCubeFace(state.image_shadow_texture_ny, CubeFace::NegativeY, info);
BindCubeFace(state.image_shadow_texture_pz, CubeFace::PositiveZ, info);
BindCubeFace(state.image_shadow_texture_nz, CubeFace::NegativeZ, info);
continue;
}
case TextureType::TextureCube:
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;
state.texture_cube_unit.texture_cube =
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;
}
state.texture_cube_unit.texture_cube = 0;
}
texture_samplers[texture_index].SyncWithConfig(texture.config);
Surface surface = res_cache.GetTextureSurface(texture);
if (surface != nullptr) {
CheckBarrier(state.texture_units[texture_index].texture_2d =
surface->texture.handle);
} else {
// Can occur when texture addr is null or its memory is unmapped/invalid
// HACK: In this case, the correct behaviour for the PICA is to use the last
// rendered colour. But because this would be impractical to implement, the
// next best alternative is to use a clear texture, essentially skipping
// the geometry in question.
// For example: a bug in Pokemon X/Y causes NULL-texture squares to be drawn
// on the male character's face, which in the OpenGL default appear black.
state.texture_units[texture_index].texture_2d = default_texture;
}
} else {
state.texture_units[texture_index].texture_2d = 0;
}
}
OGLTexture temp_tex;
if (need_duplicate_texture) {
const auto& tuple = GetFormatTuple(color_surface->pixel_format);
const GLsizei levels = color_surface->max_level + 1;
// The game is trying to use a surface as a texture and framebuffer at the same time
// which causes unpredictable behavior on the host.
// Making a copy to sample from eliminates this issue and seems to be fairly cheap.
temp_tex.Create();
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,
color_surface->GetScaledWidth(), color_surface->GetScaledHeight());
for (auto& unit : state.texture_units) {
if (unit.texture_2d == color_surface->texture.handle) {
unit.texture_2d = temp_tex.handle;
}
}
for (auto shadow_unit : {&state.image_shadow_texture_nx, &state.image_shadow_texture_ny,
&state.image_shadow_texture_nz, &state.image_shadow_texture_px,
&state.image_shadow_texture_py, &state.image_shadow_texture_pz}) {
if (*shadow_unit == color_surface->texture.handle) {
*shadow_unit = temp_tex.handle;
}
}
}
SyncTextureUnits(framebuffer);
// Sync and bind the shader
if (shader_dirty) {
@ -643,17 +469,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
// Sync the uniform data
UploadUniforms(accelerate);
// Viewport can have negative offsets or larger
// dimensions than our framebuffer sub-rect.
// Enable scissor test to prevent drawing
// outside of the framebuffer region
state.scissor.enabled = true;
state.scissor.x = draw_rect.left;
state.scissor.y = draw_rect.bottom;
state.scissor.width = draw_rect.GetWidth();
state.scissor.height = draw_rect.GetHeight();
state.Apply();
// Draw the vertex batch
bool succeeded = true;
if (accelerate) {
@ -671,12 +486,11 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
base_vertex += max_vertices) {
const std::size_t vertices = std::min(max_vertices, vertex_batch.size() - base_vertex);
const std::size_t vertex_size = vertices * sizeof(HardwareVertex);
u8* vbo;
GLintptr offset;
std::tie(vbo, offset, std::ignore) =
vertex_buffer.Map(vertex_size, sizeof(HardwareVertex));
const auto [vbo, offset, _] = vertex_buffer.Map(vertex_size, sizeof(HardwareVertex));
std::memcpy(vbo, vertex_batch.data() + base_vertex, vertex_size);
vertex_buffer.Unmap(vertex_size);
glDrawArrays(GL_TRIANGLES, static_cast<GLint>(offset / sizeof(HardwareVertex)),
static_cast<GLsizei>(vertices));
}
@ -684,10 +498,133 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
vertex_batch.clear();
// Reset textures in rasterizer state context because the rasterizer cache might delete them
for (unsigned texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
state.texture_units[texture_index].texture_2d = 0;
if (shadow_rendering) {
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT |
GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
}
res_cache.InvalidateFramebuffer(framebuffer);
return succeeded;
}
void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) {
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
const auto pica_textures = regs.texturing.GetTextures();
for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
const auto& texture = pica_textures[texture_index];
// If the texture unit is disabled unbind the corresponding gl unit
if (!texture.enabled) {
state.texture_units[texture_index].texture_2d = 0;
continue;
}
// Handle special tex0 configurations
if (texture_index == 0) {
switch (texture.config.type.Value()) {
case TextureType::Shadow2D: {
auto surface = res_cache.GetTextureSurface(texture);
state.image_shadow_texture_px = surface->Handle();
continue;
}
case TextureType::ShadowCube: {
BindShadowCube(texture);
continue;
}
case TextureType::TextureCube: {
BindTextureCube(texture);
continue;
}
default:
UnbindSpecial();
}
}
// Sync texture unit sampler
texture_samplers[texture_index].SyncWithConfig(texture.config);
// Bind the texture provided by the rasterizer cache
auto surface = res_cache.GetTextureSurface(texture);
if (!surface) {
// Can occur when texture addr is null or its memory is unmapped/invalid
// HACK: In this case, the correct behaviour for the PICA is to use the last
// rendered colour. But because this would be impractical to implement, the
// next best alternative is to use a clear texture, essentially skipping
// the geometry in question.
// For example: a bug in Pokemon X/Y causes NULL-texture squares to be drawn
// on the male character's face, which in the OpenGL default appear black.
state.texture_units[texture_index].texture_2d = default_texture;
} else if (!IsFeedbackLoop(texture_index, framebuffer, *surface)) {
state.texture_units[texture_index].texture_2d = surface->Handle();
}
}
}
void RasterizerOpenGL::BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
using CubeFace = Pica::TexturingRegs::CubeFace;
auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format);
constexpr std::array faces = {
CubeFace::PositiveX, CubeFace::NegativeX, CubeFace::PositiveY,
CubeFace::NegativeY, CubeFace::PositiveZ, CubeFace::NegativeZ,
};
for (CubeFace face : faces) {
const u32 binding = static_cast<u32>(face);
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
auto surface = res_cache.GetTextureSurface(info);
state.image_shadow_texture[binding] = surface->Handle();
}
}
void RasterizerOpenGL::BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
using CubeFace = Pica::TexturingRegs::CubeFace;
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,
.levels = texture.config.lod.max_level + 1,
.format = texture.format,
};
auto surface = res_cache.GetTextureCube(config);
texture_cube_sampler.SyncWithConfig(texture.config);
state.texture_cube_unit.texture_cube = surface->Handle();
state.texture_units[0].texture_2d = 0;
}
bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer,
Surface& surface) {
const GLuint color_attachment = framebuffer.Attachment(SurfaceType::Color);
const bool is_feedback_loop = color_attachment == surface.Handle();
if (!is_feedback_loop) {
return false;
}
// Make a temporary copy of the framebuffer to sample from
Surface temp_surface{runtime, framebuffer.ColorParams()};
const VideoCore::TextureCopy copy = {
.src_level = 0,
.dst_level = 0,
.src_layer = 0,
.dst_layer = 0,
.src_offset = {0, 0},
.dst_offset = {0, 0},
.extent = {temp_surface.GetScaledWidth(), temp_surface.GetScaledHeight()},
};
runtime.CopyTextures(surface, temp_surface, copy);
state.texture_units[texture_index].texture_2d = temp_surface.Handle();
return true;
}
void RasterizerOpenGL::UnbindSpecial() {
state.texture_cube_unit.texture_cube = 0;
state.image_shadow_texture_px = 0;
state.image_shadow_texture_nx = 0;
@ -697,29 +634,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
state.image_shadow_texture_nz = 0;
state.image_shadow_buffer = 0;
state.Apply();
if (shadow_rendering) {
glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT |
GL_TEXTURE_UPDATE_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);
}
// Mark framebuffer surfaces as dirty
Common::Rectangle<u32> draw_rect_unscaled{draw_rect.left / res_scale, draw_rect.top / res_scale,
draw_rect.right / res_scale,
draw_rect.bottom / res_scale};
if (color_surface != nullptr && write_color_fb) {
auto interval = color_surface->GetSubRectInterval(draw_rect_unscaled);
res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
color_surface);
}
if (depth_surface != nullptr && write_depth_fb) {
auto interval = depth_surface->GetSubRectInterval(draw_rect_unscaled);
res_cache.InvalidateRegion(boost::icl::first(interval), boost::icl::length(interval),
depth_surface);
}
return succeeded;
}
void RasterizerOpenGL::NotifyFixedFunctionPicaRegisterChanged(u32 id) {
@ -815,148 +729,15 @@ void RasterizerOpenGL::ClearAll(bool flush) {
}
bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
MICROPROFILE_SCOPE(OpenGL_Blits);
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.UpdateParams();
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.UpdateParams();
Common::Rectangle<u32> src_rect;
Surface src_surface;
std::tie(src_surface, src_rect) =
res_cache.GetSurfaceSubRect(src_params, 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)
return false;
if (src_surface->is_tiled != dst_surface->is_tiled)
std::swap(src_rect.top, src_rect.bottom);
if (config.flip_vertically)
std::swap(src_rect.top, src_rect.bottom);
if (!res_cache.BlitSurfaces(src_surface, src_rect, dst_surface, dst_rect))
return false;
res_cache.InvalidateRegion(dst_params.addr, dst_params.size, dst_surface);
return true;
return res_cache.AccelerateDisplayTransfer(config);
}
bool RasterizerOpenGL::AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) {
u32 copy_size = Common::AlignDown(config.texture_copy.size, 16);
if (copy_size == 0) {
return false;
}
u32 input_gap = config.texture_copy.input_gap * 16;
u32 input_width = config.texture_copy.input_width * 16;
if (input_width == 0 && input_gap != 0) {
return false;
}
if (input_gap == 0 || input_width >= copy_size) {
input_width = copy_size;
input_gap = 0;
}
if (copy_size % input_width != 0) {
return false;
}
u32 output_gap = config.texture_copy.output_gap * 16;
u32 output_width = config.texture_copy.output_width * 16;
if (output_width == 0 && output_gap != 0) {
return false;
}
if (output_gap == 0 || output_width >= copy_size) {
output_width = copy_size;
output_gap = 0;
}
if (copy_size % output_width != 0) {
return false;
}
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
src_params.height = copy_size / input_width;
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);
if (src_surface == nullptr) {
return false;
}
if (output_gap != 0 &&
(output_width != src_surface->BytesInPixels(src_rect.GetWidth() / src_surface->res_scale) *
(src_surface->is_tiled ? 8 : 1) ||
output_gap % src_surface->BytesInPixels(src_surface->is_tiled ? 64 : 1) != 0)) {
return false;
}
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(
src_surface->is_tiled ? output_gap / 8 : output_gap);
dst_params.height = src_rect.GetHeight() / src_surface->res_scale;
dst_params.res_scale = src_surface->res_scale;
dst_params.UpdateParams();
// 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);
if (dst_surface == nullptr) {
return false;
}
if (dst_surface->type == SurfaceType::Texture) {
return false;
}
if (!res_cache.BlitSurfaces(src_surface, src_rect, dst_surface, dst_rect)) {
return false;
}
res_cache.InvalidateRegion(dst_params.addr, dst_params.size, dst_surface);
return true;
return res_cache.AccelerateTextureCopy(config);
}
bool RasterizerOpenGL::AccelerateFill(const GPU::Regs::MemoryFillConfig& config) {
Surface dst_surface = res_cache.GetFillSurface(config);
if (dst_surface == nullptr)
return false;
res_cache.InvalidateRegion(dst_surface->addr, dst_surface->size, dst_surface);
return true;
return res_cache.AccelerateFill(config);
}
bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& config,
@ -967,32 +748,30 @@ 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);
auto [src_surface, src_rect] =
res_cache.GetSurfaceSubRect(src_params, VideoCore::ScaleMatch::Ignore, true);
if (src_surface == nullptr) {
return false;
}
u32 scaled_width = src_surface->GetScaledWidth();
u32 scaled_height = src_surface->GetScaledHeight();
const u32 scaled_width = src_surface->GetScaledWidth();
const u32 scaled_height = src_surface->GetScaledHeight();
screen_info.display_texcoords = Common::Rectangle<float>(
(float)src_rect.bottom / (float)scaled_height, (float)src_rect.left / (float)scaled_width,
(float)src_rect.top / (float)scaled_height, (float)src_rect.right / (float)scaled_width);
screen_info.display_texture = src_surface->texture.handle;
screen_info.display_texture = src_surface->Handle();
return true;
}
@ -1003,7 +782,6 @@ void RasterizerOpenGL::SamplerInfo::Create() {
wrap_s = wrap_t = TextureConfig::Repeat;
border_color = 0;
lod_min = lod_max = 0;
lod_bias = 0;
// default is 1000 and -1000
// Other attributes have correct defaults
@ -1021,23 +799,12 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureMagFilterMode(mag_filter));
}
// TODO(wwylele): remove new_supress_mipmap_for_cube logic once mipmap for cube is implemented
bool new_supress_mipmap_for_cube =
config.type == Pica::TexturingRegs::TextureConfig::TextureCube;
if (min_filter != config.min_filter || mip_filter != config.mip_filter ||
supress_mipmap_for_cube != new_supress_mipmap_for_cube) {
if (min_filter != config.min_filter || mip_filter != config.mip_filter) {
min_filter = config.min_filter;
mip_filter = config.mip_filter;
supress_mipmap_for_cube = new_supress_mipmap_for_cube;
if (new_supress_mipmap_for_cube) {
// HACK: use mag filter converter for min filter because they are the same anyway
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
PicaToGL::TextureMagFilterMode(min_filter));
} else {
glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
PicaToGL::TextureMinFilterMode(min_filter, mip_filter));
}
}
if (wrap_s != config.wrap_s) {
wrap_s = config.wrap_s;
@ -1065,11 +832,6 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
lod_max = config.lod.max_level;
glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, static_cast<float>(lod_max));
}
if (!GLES && lod_bias != config.lod.bias) {
lod_bias = config.lod.bias;
glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias / 256.0f);
}
}
void RasterizerOpenGL::SetShader() {

View file

@ -12,7 +12,7 @@
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_stream_buffer.h"
#include "video_core/shader/shader.h"
#include "video_core/renderer_opengl/gl_texture_runtime.h"
namespace VideoCore {
class RendererBase;
@ -69,10 +69,6 @@ private:
u32 border_color;
u32 lod_min;
u32 lod_max;
s32 lod_bias;
// TODO(wwylele): remove this once mipmap for cube is implemented
bool supress_mipmap_for_cube = false;
};
/// Syncs the clip enabled status to match the PICA register
@ -115,6 +111,21 @@ private:
void SyncAndUploadLUTs();
void SyncAndUploadLUTsLF();
/// Syncs all enabled PICA texture units
void SyncTextureUnits(const Framebuffer& framebuffer);
/// Binds the PICA shadow cube required for shadow mapping
void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture);
/// Binds a texture cube to texture unit 0
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
bool IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer, Surface& surface);
/// Unbinds all special texture unit 0 texture configurations
void UnbindSpecial();
/// Upload the uniform blocks to the uniform buffer object
void UploadUniforms(bool accelerate_draw);
@ -138,7 +149,8 @@ private:
Driver& driver;
OpenGLState state;
GLuint default_texture;
RasterizerCacheOpenGL res_cache;
TextureRuntime runtime;
VideoCore::RasterizerCache res_cache;
std::unique_ptr<ShaderProgramManager> shader_program_manager;
OGLVertexArray sw_vao; // VAO for software shader draw
@ -146,6 +158,7 @@ private:
std::array<bool, 16> hw_vao_enabled_attributes{};
std::array<SamplerInfo, 3> texture_samplers;
GLsizeiptr texture_buffer_size;
OGLStreamBuffer vertex_buffer;
OGLStreamBuffer uniform_buffer;
OGLStreamBuffer index_buffer;

View file

@ -83,20 +83,6 @@ void OGLTexture::Allocate(GLenum target, GLsizei levels, GLenum internalformat,
glBindTexture(GL_TEXTURE_2D, old_tex);
}
void OGLTexture::CopyFrom(const OGLTexture& other, GLenum target, GLsizei levels, GLsizei width,
GLsizei height) {
GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, handle);
for (GLsizei level = 0; level < levels; level++) {
glCopyImageSubData(other.handle, target, level, 0, 0, 0, handle, target, level, 0, 0, 0,
width >> level, height >> level, 1);
}
glBindTexture(GL_TEXTURE_2D, old_tex);
}
void OGLSampler::Create() {
if (handle != 0) {
return;
@ -117,10 +103,10 @@ void OGLSampler::Release() {
handle = 0;
}
void OGLShader::Create(const char* source, GLenum type) {
void OGLShader::Create(std::string_view source, GLenum type) {
if (handle != 0)
return;
if (source == nullptr)
if (source.empty())
return;
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
@ -144,7 +130,7 @@ void OGLProgram::Create(bool separable_program, const std::vector<GLuint>& shade
handle = LoadProgram(separable_program, shaders);
}
void OGLProgram::Create(const char* vert_shader, const char* frag_shader) {
void OGLProgram::Create(std::string_view vert_shader, std::string_view frag_shader) {
OGLShader vert, frag;
vert.Create(vert_shader, GL_VERTEX_SHADER);
frag.Create(frag_shader, GL_FRAGMENT_SHADER);

View file

@ -61,9 +61,6 @@ public:
void Allocate(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width,
GLsizei height = 1, GLsizei depth = 1);
void CopyFrom(const OGLTexture& other, GLenum target, GLsizei levels, GLsizei width,
GLsizei height);
GLuint handle = 0;
};
@ -108,7 +105,7 @@ public:
return *this;
}
void Create(const char* source, GLenum type);
void Create(std::string_view source, GLenum type);
void Release();
@ -135,7 +132,7 @@ public:
void Create(bool separable_program, const std::vector<GLuint>& shaders);
/// Creates a new program from given shader soruce code
void Create(const char* vert_shader, const char* frag_shader);
void Create(std::string_view vert_shader, std::string_view frag_shader);
/// Deletes the internal OpenGL resource
void Release();

View file

@ -262,7 +262,8 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un
// Only unit 0 respects the texturing type
switch (state.texture0_type) {
case TexturingRegs::TextureConfig::Texture2D:
return "textureLod(tex0, texcoord0, getLod(texcoord0 * vec2(textureSize(tex0, 0))))";
return "textureLod(tex0, texcoord0, getLod(texcoord0 * vec2(textureSize(tex0, 0))) + "
"tex_lod_bias[0])";
case TexturingRegs::TextureConfig::Projection2D:
// TODO (wwylele): find the exact LOD formula for projection texture
return "textureProj(tex0, vec3(texcoord0, texcoord0_w))";
@ -280,12 +281,15 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un
return "texture(tex0, texcoord0)";
}
case 1:
return "textureLod(tex1, texcoord1, getLod(texcoord1 * vec2(textureSize(tex1, 0))))";
return "textureLod(tex1, texcoord1, getLod(texcoord1 * vec2(textureSize(tex1, 0))) + "
"tex_lod_bias[1])";
case 2:
if (state.texture2_use_coord1)
return "textureLod(tex2, texcoord1, getLod(texcoord1 * vec2(textureSize(tex2, 0))))";
return "textureLod(tex2, texcoord1, getLod(texcoord1 * vec2(textureSize(tex2, 0))) + "
"tex_lod_bias[2])";
else
return "textureLod(tex2, texcoord2, getLod(texcoord2 * vec2(textureSize(tex2, 0))))";
return "textureLod(tex2, texcoord2, getLod(texcoord2 * vec2(textureSize(tex2, 0))) + "
"tex_lod_bias[2])";
case 3:
if (state.proctex.enable) {
return "ProcTex()";

View file

@ -13,7 +13,7 @@
namespace OpenGL {
GLuint LoadShader(const char* source, GLenum type) {
GLuint LoadShader(std::string_view source, GLenum type) {
const std::string version = GLES ? R"(#version 320 es
#define CITRA_GLES
@ -28,7 +28,7 @@ GLuint LoadShader(const char* source, GLenum type) {
)"
: "#version 430 core\n";
const char* debug_type;
std::string_view debug_type;
switch (type) {
case GL_VERTEX_SHADER:
debug_type = "vertex";
@ -43,9 +43,11 @@ GLuint LoadShader(const char* source, GLenum type) {
UNREACHABLE();
}
std::array<const char*, 2> src_arr{version.data(), source};
std::array<const GLchar*, 2> src_arr{version.data(), source.data()};
std::array<GLint, 2> lengths{static_cast<GLint>(version.size()),
static_cast<GLint>(source.size())};
GLuint shader_id = glCreateShader(type);
glShaderSource(shader_id, static_cast<GLsizei>(src_arr.size()), src_arr.data(), nullptr);
glShaderSource(shader_id, static_cast<GLsizei>(src_arr.size()), src_arr.data(), lengths.data());
LOG_DEBUG(Render_OpenGL, "Compiling {} shader...", debug_type);
glCompileShader(shader_id);

View file

@ -29,7 +29,7 @@ precision mediump uimage2D;
* @param source String of the GLSL shader program
* @param type Type of the shader (GL_VERTEX_SHADER, GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER)
*/
GLuint LoadShader(const char* source, GLenum type);
GLuint LoadShader(std::string_view source, GLenum type);
/**
* Utility function to create and link an OpenGL GLSL shader program

View file

@ -116,12 +116,17 @@ public:
// GL_IMAGE_BINDING_NAME
GLuint image_shadow_buffer;
union {
std::array<GLuint, 6> image_shadow_texture;
struct {
GLuint image_shadow_texture_px;
GLuint image_shadow_texture_nx;
GLuint image_shadow_texture_py;
GLuint image_shadow_texture_ny;
GLuint image_shadow_texture_pz;
GLuint image_shadow_texture_nz;
};
};
struct {
GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING

View file

@ -54,7 +54,7 @@ GLsizeiptr OGLStreamBuffer::GetSize() const {
}
std::tuple<u8*, GLintptr, bool> OGLStreamBuffer::Map(GLsizeiptr size, GLintptr alignment) {
ASSERT(size <= buffer_size);
ASSERT_MSG(size <= buffer_size, "Requested size {} exceeds buffer size {}", size, buffer_size);
ASSERT(alignment <= buffer_size);
mapped_size = size;

View file

@ -0,0 +1,598 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/scope_exit.h"
#include "common/settings.h"
#include "video_core/regs.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_driver.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/gl_texture_runtime.h"
namespace OpenGL {
namespace {
using VideoCore::PixelFormat;
using VideoCore::SurfaceType;
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
static constexpr std::array<FormatTuple, 4> DEPTH_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> COLOR_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
}};
static constexpr std::array<FormatTuple, 5> COLOR_TUPLES_OES = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
{GL_RGBA8, GL_RGBA, 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
}};
struct FramebufferInfo {
GLuint color;
GLuint depth;
u32 color_level;
u32 depth_level;
};
[[nodiscard]] 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;
}
[[nodiscard]] u32 FboIndex(VideoCore::SurfaceType type) {
switch (type) {
case VideoCore::SurfaceType::Color:
case VideoCore::SurfaceType::Texture:
case VideoCore::SurfaceType::Fill:
return 0;
case VideoCore::SurfaceType::Depth:
return 1;
case VideoCore::SurfaceType::DepthStencil:
return 2;
default:
UNREACHABLE_MSG("Invalid surface type!");
}
return 0;
}
[[nodiscard]] OGLTexture MakeHandle(GLenum target, u32 width, u32 height, u32 levels,
const FormatTuple& tuple, std::string_view debug_name = "") {
OGLTexture texture{};
texture.Create();
glBindTexture(target, texture.handle);
glTexStorage2D(target, levels, tuple.internal_format, width, height);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
if (!debug_name.empty()) {
glObjectLabel(GL_TEXTURE, texture.handle, -1, debug_name.data());
}
return texture;
}
} // Anonymous namespace
TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& renderer)
: driver{driver_}, blit_helper{*this} {
for (std::size_t i = 0; i < draw_fbos.size(); ++i) {
draw_fbos[i].Create();
read_fbos[i].Create();
}
auto Register = [this](PixelFormat dest, std::unique_ptr<FormatReinterpreterBase>&& obj) {
const u32 dst_index = static_cast<u32>(dest);
return reinterpreters[dst_index].push_back(std::move(obj));
};
Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
}
TextureRuntime::~TextureRuntime() = default;
bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const {
const bool should_convert = pixel_format == PixelFormat::RGBA8 || // Needs byteswap
pixel_format == PixelFormat::RGB8; // Is converted to RGBA8
return driver.IsOpenGLES() && should_convert;
}
VideoCore::StagingData TextureRuntime::FindStaging(u32 size, bool upload) {
if (size > staging_buffer.size()) {
staging_buffer.resize(size);
}
return VideoCore::StagingData{
.size = size,
.mapped = std::span{staging_buffer.data(), size},
.buffer_offset = 0,
};
}
const FormatTuple& TextureRuntime::GetFormatTuple(PixelFormat pixel_format) const {
const auto type = GetFormatType(pixel_format);
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
if (type == SurfaceType::Color) {
ASSERT(format_index < COLOR_TUPLES.size());
return (driver.IsOpenGLES() ? COLOR_TUPLES_OES : COLOR_TUPLES)[format_index];
} else if (type == SurfaceType::Depth || type == 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;
}
void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) {
recycler.emplace(tag, std::move(alloc));
}
Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params) {
const u32 width = params.width;
const u32 height = params.height;
const u32 levels = params.levels;
const GLenum target = params.texture_type == VideoCore::TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP
: GL_TEXTURE_2D;
const auto& tuple = GetFormatTuple(params.pixel_format);
const HostTextureTag key = {
.tuple = tuple,
.type = params.texture_type,
.width = width,
.height = height,
.levels = levels,
.res_scale = params.res_scale,
};
if (auto it = recycler.find(key); it != recycler.end()) {
Allocation alloc = std::move(it->second);
ASSERT(alloc.res_scale == params.res_scale);
recycler.erase(it);
return alloc;
}
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
glActiveTexture(GL_TEXTURE0);
std::array<OGLTexture, 2> textures{};
std::array<GLuint, 2> handles{};
textures[0] = MakeHandle(target, width, height, levels, tuple, params.DebugName(false));
handles.fill(textures[0].handle);
if (params.res_scale != 1) {
const u32 scaled_width = params.GetScaledWidth();
const u32 scaled_height = params.GetScaledHeight();
textures[1] =
MakeHandle(target, scaled_width, scaled_height, levels, tuple, params.DebugName(true));
handles[1] = textures[1].handle;
}
glBindTexture(target, old_tex);
return Allocation{
.textures = std::move(textures),
.handles = std::move(handles),
.tuple = tuple,
.width = params.width,
.height = params.height,
.levels = params.levels,
.res_scale = params.res_scale,
};
}
bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) {
const auto prev_state = OpenGLState::GetCurState();
// Setup scissor rectangle according to the clear rectangle
OpenGLState state;
state.scissor.enabled = true;
state.scissor.x = clear.texture_rect.left;
state.scissor.y = clear.texture_rect.bottom;
state.scissor.width = clear.texture_rect.GetWidth();
state.scissor.height = clear.texture_rect.GetHeight();
state.draw.draw_framebuffer = draw_fbos[FboIndex(surface.type)].handle;
state.Apply();
switch (surface.type) {
case SurfaceType::Color:
case SurfaceType::Texture:
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
surface.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, clear.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,
surface.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, &clear.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,
surface.Handle(), clear.texture_level);
state.depth.write_mask = GL_TRUE;
state.stencil.write_mask = -1;
state.Apply();
glClearBufferfi(GL_DEPTH_STENCIL, 0, clear.value.depth, clear.value.stencil);
break;
default:
UNREACHABLE_MSG("Unknown surface type {}", surface.type);
return false;
}
prev_state.Apply();
return true;
}
bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
const VideoCore::TextureCopy& copy) {
const GLenum src_textarget = source.texture_type == VideoCore::TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP
: GL_TEXTURE_2D;
const GLenum dest_textarget =
dest.texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
glCopyImageSubData(source.Handle(), src_textarget, copy.src_level, copy.src_offset.x,
copy.src_offset.y, copy.src_layer, dest.Handle(), dest_textarget,
copy.dst_level, copy.dst_offset.x, copy.dst_offset.y, copy.dst_layer,
copy.extent.width, copy.extent.height, 1);
return true;
}
bool TextureRuntime::BlitTextures(Surface& source, Surface& dest,
const VideoCore::TextureBlit& blit) {
OpenGLState state = OpenGLState::GetCurState();
state.scissor.enabled = false;
state.draw.read_framebuffer = read_fbos[FboIndex(source.type)].handle;
state.draw.draw_framebuffer = draw_fbos[FboIndex(dest.type)].handle;
state.Apply();
source.Attach(GL_READ_FRAMEBUFFER, blit.src_level, blit.src_layer);
dest.Attach(GL_DRAW_FRAMEBUFFER, blit.dst_level, blit.dst_layer);
// 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(source.type);
const GLenum filter = buffer_mask == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST;
glBlitFramebuffer(blit.src_rect.left, blit.src_rect.bottom, blit.src_rect.right,
blit.src_rect.top, blit.dst_rect.left, blit.dst_rect.bottom,
blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter);
return true;
}
void TextureRuntime::GenerateMipmaps(Surface& surface, u32 max_level) {
OpenGLState state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = surface.Handle();
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level);
glGenerateMipmap(GL_TEXTURE_2D);
}
const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations(
PixelFormat dest_format) const {
return reinterpreters[static_cast<u32>(dest_format)];
}
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
: SurfaceBase{params}, driver{&runtime_.GetDriver()}, runtime{&runtime_} {
if (pixel_format == PixelFormat::Invalid) {
return;
}
alloc = runtime->Allocate(params);
}
Surface::~Surface() {
if (pixel_format == PixelFormat::Invalid || !alloc) {
return;
}
const HostTextureTag tag = {
.tuple = alloc.tuple,
.type = texture_type,
.width = alloc.width,
.height = alloc.height,
.levels = alloc.levels,
.res_scale = alloc.res_scale,
};
runtime->Recycle(tag, std::move(alloc));
}
void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
const VideoCore::StagingData& staging) {
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0);
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
const u32 unscaled_width = upload.texture_rect.GetWidth();
const u32 unscaled_height = upload.texture_rect.GetHeight();
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
// Bind the unscaled texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, Handle(false));
// Upload the requested rectangle of pixels
const auto& tuple = runtime->GetFormatTuple(pixel_format);
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left,
upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format,
tuple.type, staging.mapped.data());
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, old_tex);
const VideoCore::TextureBlit blit = {
.src_level = upload.texture_level,
.dst_level = upload.texture_level,
.src_rect = upload.texture_rect,
.dst_rect = upload.texture_rect * res_scale,
};
// If texture filtering is enabled attempt to upscale with that, otherwise fallback
// to normal blit.
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
}
void Surface::Download(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging) {
// Ensure no bad interactions with GL_PACK_ALIGNMENT
ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0);
const u32 unscaled_width = download.texture_rect.GetWidth();
const u32 unscaled_height = download.texture_rect.GetHeight();
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
// Scale down upscaled data before downloading it
if (res_scale != 1) {
const VideoCore::TextureBlit blit = {
.src_level = download.texture_level,
.dst_level = download.texture_level,
.src_rect = download.texture_rect * res_scale,
.dst_rect = download.texture_rect,
};
BlitScale(blit, false);
}
// Try to download without using an fbo. This should succeed on recent desktop drivers
if (DownloadWithoutFbo(download, staging)) {
return;
}
const u32 fbo_index = FboIndex(type);
OpenGLState state = OpenGLState::GetCurState();
state.scissor.enabled = false;
state.draw.read_framebuffer = runtime->read_fbos[fbo_index].handle;
state.Apply();
Attach(GL_READ_FRAMEBUFFER, download.texture_level, 0, false);
// Read the pixel data to the staging buffer
const auto& tuple = runtime->GetFormatTuple(pixel_format);
glReadPixels(download.texture_rect.left, download.texture_rect.bottom, unscaled_width,
unscaled_height, tuple.format, tuple.type, staging.mapped.data());
// Restore previous state
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging) {
// If a partial download is requested and ARB_get_texture_sub_image (core in 4.5)
// is not available we cannot proceed further.
const bool is_full_download = download.texture_rect == GetRect();
const bool has_sub_image = driver->HasArbGetTextureSubImage();
if (driver->IsOpenGLES() || (!is_full_download && !has_sub_image)) {
return false;
}
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
const auto& tuple = runtime->GetFormatTuple(pixel_format);
glActiveTexture(GL_TEXTURE0);
glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(stride));
SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); });
// Prefer glGetTextureSubImage in most cases since it's the fastest and most convenient option
if (has_sub_image) {
const GLsizei buf_size = static_cast<GLsizei>(staging.mapped.size());
glGetTextureSubImage(Handle(false), download.texture_level, download.texture_rect.left,
download.texture_rect.bottom, 0, download.texture_rect.GetWidth(),
download.texture_rect.GetHeight(), 1, tuple.format, tuple.type,
buf_size, staging.mapped.data());
return true;
}
// This should only trigger for full texture downloads in oldish intel drivers
// that only support up to 4.3
glBindTexture(GL_TEXTURE_2D, Handle(false));
glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type,
staging.mapped.data());
glBindTexture(GL_TEXTURE_2D, old_tex);
return true;
}
void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
const GLuint handle = Handle(scaled);
const GLenum textarget = texture_type == VideoCore::TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer
: GL_TEXTURE_2D;
switch (type) {
case VideoCore::SurfaceType::Color:
case VideoCore::SurfaceType::Texture:
glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, handle, level);
break;
case VideoCore::SurfaceType::Depth:
glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, handle, level);
break;
case VideoCore::SurfaceType::DepthStencil:
glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, handle, level);
break;
default:
UNREACHABLE_MSG("Invalid surface type!");
}
}
u32 Surface::GetInternalBytesPerPixel() const {
// RGB8 is converted to RGBA8 on OpenGL ES since it doesn't support BGR8
if (driver->IsOpenGLES() && pixel_format == VideoCore::PixelFormat::RGB8) {
return 4;
}
return GetFormatBytesPerPixel(pixel_format);
}
void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
const u32 fbo_index = FboIndex(type);
OpenGLState state = OpenGLState::GetCurState();
state.scissor.enabled = false;
state.draw.read_framebuffer = runtime->read_fbos[fbo_index].handle;
state.draw.draw_framebuffer = runtime->draw_fbos[fbo_index].handle;
state.Apply();
Attach(GL_READ_FRAMEBUFFER, blit.src_level, blit.src_layer, !up_scale);
Attach(GL_DRAW_FRAMEBUFFER, blit.dst_level, blit.dst_layer, up_scale);
const GLenum buffer_mask = MakeBufferMask(type);
const GLenum filter = buffer_mask == GL_COLOR_BUFFER_BIT ? GL_LINEAR : GL_NEAREST;
glBlitFramebuffer(blit.src_rect.left, blit.src_rect.bottom, blit.src_rect.right,
blit.src_rect.top, blit.dst_rect.left, blit.dst_rect.bottom,
blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter);
}
Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level,
Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs,
Common::Rectangle<u32> surfaces_rect)
: VideoCore::FramebufferBase{regs, color, color_level,
depth_stencil, depth_level, surfaces_rect} {
const bool shadow_rendering = regs.framebuffer.IsShadowRendering();
const bool has_stencil = regs.framebuffer.HasStencil();
if (shadow_rendering && !color) {
return; // Framebuffer won't get used
}
if (color) {
attachments[0] = color->Handle();
}
if (depth_stencil) {
attachments[1] = depth_stencil->Handle();
}
const FramebufferInfo info = {
.color = attachments[0],
.depth = attachments[1],
.color_level = color_level,
.depth_level = depth_level,
};
const u64 hash = Common::ComputeHash64(&info, sizeof(FramebufferInfo));
auto [it, new_framebuffer] = runtime.framebuffer_cache.try_emplace(hash);
if (!new_framebuffer) {
handle = it->second.handle;
return;
}
const GLuint old_fbo = OpenGLState::GetCurState().draw.draw_framebuffer;
OGLFramebuffer& framebuffer = it->second;
framebuffer.Create();
handle = it->second.handle;
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer.handle);
SCOPE_EXIT({ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_fbo); });
if (shadow_rendering) {
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
color->width * res_scale);
glFramebufferParameteri(GL_DRAW_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
color->height * res_scale);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
} else {
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
color ? color->Handle() : 0, color_level);
if (depth_stencil) {
if (has_stencil) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, depth_stencil->Handle(), depth_level);
} else {
// Attach depth
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
depth_stencil->Handle(), depth_level);
// Clear stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
}
} else {
// Clear both depth and stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
}
}
}
Framebuffer::~Framebuffer() = default;
} // namespace OpenGL

View file

@ -0,0 +1,204 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/rasterizer_cache/framebuffer_base.h"
#include "video_core/rasterizer_cache/surface_base.h"
#include "video_core/renderer_opengl/gl_blit_helper.h"
#include "video_core/renderer_opengl/gl_format_reinterpreter.h"
namespace VideoCore {
class RendererBase;
}
namespace OpenGL {
struct FormatTuple {
GLint internal_format;
GLenum format;
GLenum type;
bool operator==(const FormatTuple& other) const noexcept {
return std::tie(internal_format, format, type) ==
std::tie(other.internal_format, other.format, other.type);
}
};
struct HostTextureTag {
FormatTuple tuple{};
VideoCore::TextureType type{};
u32 width = 0;
u32 height = 0;
u32 levels = 1;
u32 res_scale = 1;
bool operator==(const HostTextureTag& other) const noexcept {
return std::tie(tuple, type, width, height, levels, res_scale) ==
std::tie(other.tuple, other.type, other.width, other.height, other.levels,
other.res_scale);
}
struct Hash {
const u64 operator()(const HostTextureTag& tag) const {
return Common::ComputeHash64(&tag, sizeof(HostTextureTag));
}
};
};
struct Allocation {
std::array<OGLTexture, 2> textures;
std::array<GLuint, 2> handles;
FormatTuple tuple;
u32 width;
u32 height;
u32 levels;
u32 res_scale;
operator bool() const noexcept {
return textures[0].handle;
}
bool Matches(u32 width_, u32 height_, u32 levels_, const FormatTuple& tuple_) const {
return std::tie(width, height, levels, tuple) == std::tie(width_, height_, levels_, tuple_);
}
};
class Surface;
class Driver;
struct CachedTextureCube;
/**
* Provides texture manipulation functions to the rasterizer cache
* Separating this into a class makes it easier to abstract graphics API code
*/
class TextureRuntime {
friend class Surface;
friend class Framebuffer;
friend class BlitHelper;
public:
explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer);
~TextureRuntime();
/// Returns true if the provided pixel format cannot be used natively by the runtime.
bool NeedsConversion(VideoCore::PixelFormat pixel_format) const;
/// Maps an internal staging buffer of the provided size of pixel uploads/downloads
VideoCore::StagingData FindStaging(u32 size, bool upload);
/// Returns the OpenGL format tuple associated with the provided pixel format
const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const;
/// Takes back ownership of the allocation for recycling
void Recycle(const HostTextureTag tag, Allocation&& alloc);
/// Allocates an OpenGL texture with the specified dimentions and format
Allocation Allocate(const VideoCore::SurfaceParams& params);
/// Fills the rectangle of the texture with the clear value provided
bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear);
/// Copies a rectangle of source to another rectange of dest
bool CopyTextures(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy);
/// Blits a rectangle of source to another rectange of dest
bool BlitTextures(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
/// Generates mipmaps for all the available levels of the texture
void GenerateMipmaps(Surface& surface, u32 max_level);
/// Returns all source formats that support reinterpretation to the dest format
const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dest_format) const;
private:
/// Returns the OpenGL driver class
const Driver& GetDriver() const {
return driver;
}
private:
const Driver& driver;
BlitHelper blit_helper;
std::vector<u8> staging_buffer;
std::array<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters;
std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> recycler;
std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache;
std::array<OGLFramebuffer, 3> draw_fbos;
std::array<OGLFramebuffer, 3> read_fbos;
};
class Surface : public VideoCore::SurfaceBase {
public:
explicit Surface(TextureRuntime& runtime, const VideoCore::SurfaceParams& params);
~Surface();
Surface(const Surface&) = delete;
Surface& operator=(const Surface&) = delete;
Surface(Surface&& o) noexcept = default;
Surface& operator=(Surface&& o) noexcept = default;
/// Returns the surface image handle
GLuint Handle(bool scaled = true) const noexcept {
return alloc.handles[static_cast<u32>(scaled)];
}
/// Returns the tuple of the surface allocation.
const FormatTuple& Tuple() const noexcept {
return alloc.tuple;
}
/// Uploads pixel data in staging to a rectangle region of the surface texture
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
/// Downloads pixel data to staging from a rectangle region of the surface texture
void Download(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging);
/// Attaches a handle of surface to the specified framebuffer target
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
/// Returns the bpp of the internal surface format
u32 GetInternalBytesPerPixel() const;
private:
/// Performs blit between the scaled/unscaled images
void BlitScale(const VideoCore::TextureBlit& blit, bool up_scale);
/// Attempts to download without using an fbo
bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging);
private:
const Driver* driver;
TextureRuntime* runtime;
Allocation alloc{};
};
class Framebuffer : public VideoCore::FramebufferBase {
public:
explicit Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level,
Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs,
Common::Rectangle<u32> surfaces_rect);
~Framebuffer();
[[nodiscard]] GLuint Handle() const noexcept {
return handle;
}
[[nodiscard]] GLuint Attachment(VideoCore::SurfaceType type) const noexcept {
return attachments[Index(type)];
}
[[nodiscard]] bool HasAttachment(VideoCore::SurfaceType type) const noexcept {
return static_cast<bool>(attachments[Index(type)]);
}
private:
std::array<GLuint, 2> attachments{};
GLuint handle{};
};
} // namespace OpenGL

View file

@ -21,8 +21,16 @@
#include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/video_core.h"
#include "video_core/host_shaders/opengl_present_anaglyph_frag.h"
#include "video_core/host_shaders/opengl_present_frag.h"
#include "video_core/host_shaders/opengl_present_interlaced_frag.h"
#include "video_core/host_shaders/opengl_present_vert.h"
namespace OpenGL {
MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have
// to wait on available presentation frames. There doesn't seem to be much of a downside to a larger
// number but 9 swap textures at 60FPS presentation allows for 800% speed so thats probably fine
@ -48,7 +56,7 @@ public:
std::deque<Frontend::Frame*> present_queue{};
Frontend::Frame* previous_frame = nullptr;
OGLTextureMailbox() {
OGLTextureMailbox(bool has_debug_tool_ = false) : has_debug_tool{has_debug_tool_} {
for (auto& frame : swap_chain) {
free_queue.push(&frame);
}
@ -126,6 +134,8 @@ public:
std::unique_lock<std::mutex> lock(swap_chain_lock);
present_queue.push_front(frame);
present_cv.notify_one();
DebugNotifyNextFrame();
}
// This is virtual as it is to be overriden in OGLVideoDumpingMailbox below.
@ -148,6 +158,8 @@ public:
}
Frontend::Frame* TryGetPresentFrame(int timeout_ms) override {
DebugWaitForNextFrame();
std::unique_lock<std::mutex> lock(swap_chain_lock);
// wait for new entries in the present_queue
present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms),
@ -160,6 +172,33 @@ public:
LoadPresentFrame();
return previous_frame;
}
private:
std::mutex debug_synch_mutex;
std::condition_variable debug_synch_condition;
std::atomic_int frame_for_debug{};
const bool has_debug_tool; // When true, using a GPU debugger, so keep frames in lock-step
/// Signal that a new frame is available (called from GPU thread)
void DebugNotifyNextFrame() {
if (!has_debug_tool) {
return;
}
frame_for_debug++;
std::lock_guard lock{debug_synch_mutex};
debug_synch_condition.notify_one();
}
/// Wait for a new frame to be available (called from presentation thread)
void DebugWaitForNextFrame() {
if (!has_debug_tool) {
return;
}
const int last_frame = frame_for_debug;
std::unique_lock lock{debug_synch_mutex};
debug_synch_condition.wait(lock,
[this, last_frame] { return frame_for_debug > last_frame; });
}
};
/// This mailbox is different in that it will never discard rendered frames
@ -218,93 +257,6 @@ public:
}
};
static const char vertex_shader[] = R"(
in vec2 vert_position;
in vec2 vert_tex_coord;
out vec2 frag_tex_coord;
// This is a truncated 3x3 matrix for 2D transformations:
// The upper-left 2x2 submatrix performs scaling/rotation/mirroring.
// The third column performs translation.
// The third row could be used for projection, which we don't need in 2D. It hence is assumed to
// implicitly be [0, 0, 1]
uniform mat3x2 modelview_matrix;
void main() {
// Multiply input position by the rotscale part of the matrix and then manually translate by
// the last column. This is equivalent to using a full 3x3 matrix and expanding the vector
// to `vec3(vert_position.xy, 1.0)`
gl_Position = vec4(mat2(modelview_matrix) * vert_position + modelview_matrix[2], 0.0, 1.0);
frag_tex_coord = vert_tex_coord;
}
)";
static const char fragment_shader[] = R"(
in vec2 frag_tex_coord;
layout(location = 0) out vec4 color;
uniform vec4 i_resolution;
uniform vec4 o_resolution;
uniform int layer;
uniform sampler2D color_texture;
void main() {
color = texture(color_texture, frag_tex_coord);
}
)";
static const char fragment_shader_anaglyph[] = R"(
// Anaglyph Red-Cyan shader based on Dubois algorithm
// Constants taken from the paper:
// "Conversion of a Stereo Pair to Anaglyph with
// the Least-Squares Projection Method"
// Eric Dubois, March 2009
const mat3 l = mat3( 0.437, 0.449, 0.164,
-0.062,-0.062,-0.024,
-0.048,-0.050,-0.017);
const mat3 r = mat3(-0.011,-0.032,-0.007,
0.377, 0.761, 0.009,
-0.026,-0.093, 1.234);
in vec2 frag_tex_coord;
out vec4 color;
uniform vec4 resolution;
uniform int layer;
uniform sampler2D color_texture;
uniform sampler2D color_texture_r;
void main() {
vec4 color_tex_l = texture(color_texture, frag_tex_coord);
vec4 color_tex_r = texture(color_texture_r, frag_tex_coord);
color = vec4(color_tex_l.rgb*l+color_tex_r.rgb*r, color_tex_l.a);
}
)";
static const char fragment_shader_interlaced[] = R"(
in vec2 frag_tex_coord;
out vec4 color;
uniform vec4 o_resolution;
uniform sampler2D color_texture;
uniform sampler2D color_texture_r;
uniform int reverse_interlaced;
void main() {
float screen_row = o_resolution.x * frag_tex_coord.x;
if (int(screen_row) % 2 == reverse_interlaced)
color = texture(color_texture, frag_tex_coord);
else
color = texture(color_texture_r, frag_tex_coord);
}
)";
/**
* Vertex structure that the drawn screen rectangles are composed of.
*/
@ -354,9 +306,10 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window
Frontend::EmuWindow* secondary_window)
: VideoCore::RendererBase{system, window, secondary_window}, driver{system.TelemetrySession()},
frame_dumper{system.VideoDumper(), window} {
window.mailbox = std::make_unique<OGLTextureMailbox>();
const bool has_debug_tool = driver.HasDebugTool();
window.mailbox = std::make_unique<OGLTextureMailbox>(has_debug_tool);
if (secondary_window) {
secondary_window->mailbox = std::make_unique<OGLTextureMailbox>();
secondary_window->mailbox = std::make_unique<OGLTextureMailbox>(has_debug_tool);
}
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
InitOpenGLObjects();
@ -365,10 +318,6 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window
RendererOpenGL::~RendererOpenGL() = default;
MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64));
MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128));
/// Swap buffers (render frame)
void RendererOpenGL::SwapBuffers() {
// Maintain the rasterizer's state as a priority
OpenGLState prev_state = OpenGLState::GetCurState();
@ -673,13 +622,13 @@ void RendererOpenGL::ReloadShader() {
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph) {
if (Settings::values.anaglyph_shader_name.GetValue() == "dubois (builtin)") {
shader_data += fragment_shader_anaglyph;
shader_data += HostShaders::OPENGL_PRESENT_ANAGLYPH_FRAG;
} else {
std::string shader_text = OpenGL::GetPostProcessingShaderCode(
true, Settings::values.anaglyph_shader_name.GetValue());
if (shader_text.empty()) {
// Should probably provide some information that the shader couldn't load
shader_data += fragment_shader_anaglyph;
shader_data += HostShaders::OPENGL_PRESENT_ANAGLYPH_FRAG;
} else {
shader_data += shader_text;
}
@ -687,22 +636,22 @@ void RendererOpenGL::ReloadShader() {
} else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d.GetValue() ==
Settings::StereoRenderOption::ReverseInterlaced) {
shader_data += fragment_shader_interlaced;
shader_data += HostShaders::OPENGL_PRESENT_INTERLACED_FRAG;
} else {
if (Settings::values.pp_shader_name.GetValue() == "none (builtin)") {
shader_data += fragment_shader;
shader_data += HostShaders::OPENGL_PRESENT_FRAG;
} else {
std::string shader_text = OpenGL::GetPostProcessingShaderCode(
false, Settings::values.pp_shader_name.GetValue());
if (shader_text.empty()) {
// Should probably provide some information that the shader couldn't load
shader_data += fragment_shader;
shader_data += HostShaders::OPENGL_PRESENT_FRAG;
} else {
shader_data += shader_text;
}
}
}
shader.Create(vertex_shader, shader_data.c_str());
shader.Create(HostShaders::OPENGL_PRESENT_VERT, shader_data);
state.draw.shader_program = shader.handle;
state.Apply();
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");

View file

@ -1,253 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <chrono>
#include <vector>
#include <fmt/chrono.h>
#include "common/logging/log.h"
#include "video_core/rasterizer_cache/rasterizer_cache_utils.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_downloader_es.h"
#include "shaders/depth_to_color.frag"
#include "shaders/depth_to_color.vert"
#include "shaders/ds_to_color.frag"
namespace OpenGL {
/**
* Self tests for the texture downloader
*/
void TextureDownloaderES::Test() {
auto cur_state = OpenGLState::GetCurState();
OpenGLState state;
{
GLint range[2];
GLint precision;
#define PRECISION_TEST(type) \
glGetShaderPrecisionFormat(GL_FRAGMENT_SHADER, type, range, &precision); \
LOG_INFO(Render_OpenGL, #type " range: [{}, {}], precision: {}", range[0], range[1], precision);
PRECISION_TEST(GL_LOW_INT);
PRECISION_TEST(GL_MEDIUM_INT);
PRECISION_TEST(GL_HIGH_INT);
PRECISION_TEST(GL_LOW_FLOAT);
PRECISION_TEST(GL_MEDIUM_FLOAT);
PRECISION_TEST(GL_HIGH_FLOAT);
#undef PRECISION_TEST
}
glActiveTexture(GL_TEXTURE0);
const auto test = [this, &state](FormatTuple tuple, auto original_data, std::size_t tex_size,
auto data_generator) {
OGLTexture texture;
texture.Create();
state.texture_units[0].texture_2d = texture.handle;
state.Apply();
original_data.resize(tex_size * tex_size);
for (std::size_t idx = 0; idx < original_data.size(); ++idx) {
original_data[idx] = data_generator(idx);
}
GLsizei tex_sizei = static_cast<GLsizei>(tex_size);
glTexStorage2D(GL_TEXTURE_2D, 1, tuple.internal_format, tex_sizei, tex_sizei);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex_sizei, tex_sizei, tuple.format, tuple.type,
original_data.data());
decltype(original_data) new_data(original_data.size());
glFinish();
auto start = std::chrono::high_resolution_clock::now();
GetTexImage(GL_TEXTURE_2D, 0, tuple.format, tuple.type, tex_sizei, tex_sizei,
new_data.data());
glFinish();
auto time = std::chrono::high_resolution_clock::now() - start;
LOG_INFO(Render_OpenGL, "test took {}", std::chrono::duration<double, std::milli>(time));
int diff = 0;
for (std::size_t idx = 0; idx < original_data.size(); ++idx)
if (new_data[idx] - original_data[idx] != diff) {
diff = new_data[idx] - original_data[idx];
// every time the error between the real and expected value changes, log it
// some error is expected in D24 due to floating point precision
LOG_WARNING(Render_OpenGL, "difference changed at {:#X}: {:#X} -> {:#X}", idx,
original_data[idx], new_data[idx]);
}
};
LOG_INFO(Render_OpenGL, "GL_DEPTH24_STENCIL8 download test starting");
test(GetFormatTuple(PixelFormat::D24S8), std::vector<u32>{}, 4096,
[](std::size_t idx) { return static_cast<u32>((idx << 8) | (idx & 0xFF)); });
LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT24 download test starting");
test(GetFormatTuple(PixelFormat::D24), std::vector<u32>{}, 4096,
[](std::size_t idx) { return static_cast<u32>(idx << 8); });
LOG_INFO(Render_OpenGL, "GL_DEPTH_COMPONENT16 download test starting");
test(GetFormatTuple(PixelFormat::D16), std::vector<u16>{}, 256,
[](std::size_t idx) { return static_cast<u16>(idx); });
cur_state.Apply();
}
TextureDownloaderES::TextureDownloaderES(bool enable_depth_stencil) {
vao.Create();
read_fbo_generic.Create();
depth32_fbo.Create();
r32ui_renderbuffer.Create();
depth16_fbo.Create();
r16_renderbuffer.Create();
const auto init_program = [](ConversionShader& converter, std::string_view frag) {
converter.program.Create(depth_to_color_vert.data(), frag.data());
converter.lod_location = glGetUniformLocation(converter.program.handle, "lod");
};
// xperia64: The depth stencil shader currently uses a GLES extension that is not supported
// across all devices Reportedly broken on Tegra devices and the Nexus 6P, so enabling it can be
// toggled
if (enable_depth_stencil) {
init_program(d24s8_r32ui_conversion_shader, ds_to_color_frag);
}
init_program(d24_r32ui_conversion_shader, depth_to_color_frag);
init_program(d16_r16_conversion_shader, R"(
out highp float color;
uniform highp sampler2D depth;
uniform int lod;
void main(){
color = texelFetch(depth, ivec2(gl_FragCoord.xy), lod).x;
}
)");
sampler.Create();
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glSamplerParameteri(sampler.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
auto cur_state = OpenGLState::GetCurState();
auto state = cur_state;
state.draw.shader_program = d24s8_r32ui_conversion_shader.program.handle;
state.draw.draw_framebuffer = depth32_fbo.handle;
state.renderbuffer = r32ui_renderbuffer.handle;
state.Apply();
glRenderbufferStorage(GL_RENDERBUFFER, GL_R32UI, max_size, max_size);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
r32ui_renderbuffer.handle);
glUniform1i(glGetUniformLocation(d24s8_r32ui_conversion_shader.program.handle, "depth"), 1);
state.draw.draw_framebuffer = depth16_fbo.handle;
state.renderbuffer = r16_renderbuffer.handle;
state.Apply();
glRenderbufferStorage(GL_RENDERBUFFER, GL_R16, max_size, max_size);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
r16_renderbuffer.handle);
cur_state.Apply();
}
/**
* OpenGL ES does not support glReadBuffer for depth/stencil formats
* This gets around it by converting to a Red surface before downloading
*/
GLuint TextureDownloaderES::ConvertDepthToColor(GLuint level, GLenum& format, GLenum& type,
GLint height, GLint width) {
ASSERT(width <= max_size && height <= max_size);
const OpenGLState cur_state = OpenGLState::GetCurState();
OpenGLState state;
state.texture_units[0] = {cur_state.texture_units[0].texture_2d, sampler.handle};
state.draw.vertex_array = vao.handle;
OGLTexture texture_view;
const ConversionShader* converter;
switch (type) {
case GL_UNSIGNED_SHORT:
state.draw.draw_framebuffer = depth16_fbo.handle;
converter = &d16_r16_conversion_shader;
format = GL_RED;
break;
case GL_UNSIGNED_INT:
state.draw.draw_framebuffer = depth32_fbo.handle;
converter = &d24_r32ui_conversion_shader;
format = GL_RED_INTEGER;
break;
case GL_UNSIGNED_INT_24_8:
state.draw.draw_framebuffer = depth32_fbo.handle;
converter = &d24s8_r32ui_conversion_shader;
format = GL_RED_INTEGER;
type = GL_UNSIGNED_INT;
break;
default:
UNREACHABLE_MSG("Destination type not recognized");
}
state.draw.shader_program = converter->program.handle;
state.viewport = {0, 0, width, height};
state.Apply();
if (converter->program.handle == d24s8_r32ui_conversion_shader.program.handle) {
// TODO BreadFish64: the ARM framebuffer reading extension is probably not the most optimal
// way to do this, search for another solution
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
state.texture_units[0].texture_2d, level);
}
glUniform1i(converter->lod_location, level);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
if (texture_view.handle) {
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
}
return state.draw.draw_framebuffer;
}
/**
* OpenGL ES does not support glGetTexImage. Obtain the pixels by attaching the
* texture to a framebuffer.
* Originally from https://github.com/apitrace/apitrace/blob/master/retrace/glstate_images.cpp
* Depth texture download assumes that the texture's format tuple matches what is found
* OpenGL::depth_format_tuples
*/
void TextureDownloaderES::GetTexImage(GLenum target, GLuint level, GLenum format, GLenum type,
GLint height, GLint width, void* pixels) {
OpenGLState state = OpenGLState::GetCurState();
GLuint texture{};
const GLuint old_read_buffer = state.draw.read_framebuffer;
switch (target) {
case GL_TEXTURE_2D:
texture = state.texture_units[0].texture_2d;
break;
case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
texture = state.texture_cube_unit.texture_cube;
break;
default:
UNIMPLEMENTED_MSG("Unexpected target {:x}", target);
}
switch (format) {
case GL_DEPTH_COMPONENT:
case GL_DEPTH_STENCIL:
// unfortunately, the accurate way is too slow for release
return;
state.draw.read_framebuffer = ConvertDepthToColor(level, format, type, height, width);
state.Apply();
break;
default:
state.draw.read_framebuffer = read_fbo_generic.handle;
state.Apply();
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
level);
}
GLenum status = glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
LOG_DEBUG(Render_OpenGL, "Framebuffer is incomplete, status: {:X}", status);
}
glReadPixels(0, 0, width, height, format, type, pixels);
state.draw.read_framebuffer = old_read_buffer;
state.Apply();
}
} // namespace OpenGL

View file

@ -1,36 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "common/common_types.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
class OpenGLState;
class TextureDownloaderES {
static constexpr u16 max_size = 1024;
OGLVertexArray vao;
OGLFramebuffer read_fbo_generic;
OGLFramebuffer depth32_fbo, depth16_fbo;
OGLRenderbuffer r32ui_renderbuffer, r16_renderbuffer;
struct ConversionShader {
OGLProgram program;
GLint lod_location{-1};
} d24_r32ui_conversion_shader, d16_r16_conversion_shader, d24s8_r32ui_conversion_shader;
OGLSampler sampler;
void Test();
GLuint ConvertDepthToColor(GLuint level, GLenum& format, GLenum& type, GLint height,
GLint width);
public:
TextureDownloaderES(bool enable_depth_stencil);
void GetTexImage(GLenum target, GLuint level, GLenum format, const GLenum type, GLint height,
GLint width, void* pixels);
};
} // namespace OpenGL

View file

@ -1,138 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// modified from
// https://github.com/bloc97/Anime4K/blob/533cee5f7018d0e57ad2a26d76d43f13b9d8782a/glsl/Anime4K_Adaptive_v1.0RC2_UltraFast.glsl
// MIT License
//
// Copyright(c) 2019 bloc97
//
// Permission is hereby granted,
// free of charge,
// to any person obtaining a copy of this software and associated documentation
// files(the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and / or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions :
//
// The above copyright notice and this permission notice shall be included in all copies
// or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS",
// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "video_core/renderer_opengl/texture_filters/anime4k/anime4k_ultrafast.h"
#include "shaders/refine.frag"
#include "shaders/tex_coord.vert"
#include "shaders/x_gradient.frag"
#include "shaders/y_gradient.frag"
namespace OpenGL {
Anime4kUltrafast::Anime4kUltrafast(u16 scale_factor) : TextureFilterBase(scale_factor) {
const OpenGLState cur_state = OpenGLState::GetCurState();
vao.Create();
for (std::size_t idx = 0; idx < samplers.size(); ++idx) {
samplers[idx].Create();
state.texture_units[idx].sampler = samplers[idx].handle;
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_MIN_FILTER,
idx != 2 ? GL_LINEAR : GL_NEAREST);
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_MAG_FILTER,
idx != 2 ? GL_LINEAR : GL_NEAREST);
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(samplers[idx].handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
state.draw.vertex_array = vao.handle;
gradient_x_program.Create(tex_coord_vert.data(), x_gradient_frag.data());
gradient_y_program.Create(tex_coord_vert.data(), y_gradient_frag.data());
refine_program.Create(tex_coord_vert.data(), refine_frag.data());
state.draw.shader_program = gradient_y_program.handle;
state.Apply();
glUniform1i(glGetUniformLocation(gradient_y_program.handle, "tex_input"), 2);
state.draw.shader_program = refine_program.handle;
state.Apply();
glUniform1i(glGetUniformLocation(refine_program.handle, "LUMAD"), 1);
cur_state.Apply();
}
void Anime4kUltrafast::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
// These will have handles from the previous texture that was filtered, reset them to avoid
// binding invalid textures.
state.texture_units[0].texture_2d = 0;
state.texture_units[1].texture_2d = 0;
state.texture_units[2].texture_2d = 0;
const auto setup_temp_tex = [this, &src_rect](GLint internal_format, GLint format) {
TempTex texture;
texture.fbo.Create();
texture.tex.Create();
state.texture_units[0].texture_2d = texture.tex.handle;
state.draw.draw_framebuffer = texture.fbo.handle;
state.Apply();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture.tex.handle);
glTexStorage2D(GL_TEXTURE_2D, 1, internal_format,
src_rect.GetWidth() * internal_scale_factor,
src_rect.GetHeight() * internal_scale_factor);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
texture.tex.handle, 0);
return texture;
};
auto XY = setup_temp_tex(GL_RG16F, GL_RG);
auto LUMAD = setup_temp_tex(GL_R16F, GL_RED);
state.viewport = {static_cast<GLint>(src_rect.left * internal_scale_factor),
static_cast<GLint>(src_rect.bottom * internal_scale_factor),
static_cast<GLsizei>(src_rect.GetWidth() * internal_scale_factor),
static_cast<GLsizei>(src_rect.GetHeight() * internal_scale_factor)};
state.texture_units[0].texture_2d = src_tex.handle;
state.texture_units[1].texture_2d = LUMAD.tex.handle;
state.texture_units[2].texture_2d = XY.tex.handle;
state.draw.draw_framebuffer = XY.fbo.handle;
state.draw.shader_program = gradient_x_program.handle;
state.Apply();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// gradient y pass
state.draw.draw_framebuffer = LUMAD.fbo.handle;
state.draw.shader_program = gradient_y_program.handle;
state.Apply();
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
// refine pass
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.draw.draw_framebuffer = draw_fbo.handle;
state.draw.shader_program = refine_program.handle;
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex.handle,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
cur_state.Apply();
}
} // namespace OpenGL

View file

@ -1,38 +0,0 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_base.h"
namespace OpenGL {
class Anime4kUltrafast : public TextureFilterBase {
public:
static constexpr std::string_view NAME = "Anime4K Ultrafast";
explicit Anime4kUltrafast(u16 scale_factor);
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
static constexpr u8 internal_scale_factor = 2;
OpenGLState state{};
OGLVertexArray vao;
struct TempTex {
OGLTexture tex;
OGLFramebuffer fbo;
};
std::array<OGLSampler, 3> samplers;
OGLProgram gradient_x_program, gradient_y_program, refine_program;
};
} // namespace OpenGL

View file

@ -1,20 +0,0 @@
//? #version 330
precision mediump float;
in vec2 tex_coord;
out vec2 frag_color;
uniform sampler2D tex_input;
const vec3 K = vec3(0.2627, 0.6780, 0.0593);
// TODO: improve handling of alpha channel
#define GetLum(xoffset) dot(K, textureLodOffset(tex_input, tex_coord, 0.0, ivec2(xoffset, 0)).rgb)
void main() {
float l = GetLum(-1);
float c = GetLum(0);
float r = GetLum(1);
frag_color = vec2(r - l, l + 2.0 * c + r);
}

View file

@ -1,18 +0,0 @@
//? #version 330
precision mediump float;
in vec2 tex_coord;
out float frag_color;
uniform sampler2D tex_input;
void main() {
vec2 t = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, 1)).xy;
vec2 c = textureLod(tex_input, tex_coord, 0.0).xy;
vec2 b = textureLodOffset(tex_input, tex_coord, 0.0, ivec2(0, -1)).xy;
vec2 grad = vec2(t.x + 2.0 * c.x + b.x, b.y - t.y);
frag_color = 1.0 - length(grad);
}

View file

@ -1,46 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_opengl/texture_filters/bicubic/bicubic.h"
#include "shaders/bicubic.frag"
#include "shaders/tex_coord.vert"
namespace OpenGL {
Bicubic::Bicubic(u16 scale_factor) : TextureFilterBase(scale_factor) {
program.Create(tex_coord_vert.data(), bicubic_frag.data());
vao.Create();
src_sampler.Create();
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.draw.shader_program = program.handle;
state.texture_units[0].sampler = src_sampler.handle;
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} // namespace OpenGL
void Bicubic::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex.handle,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
cur_state.Apply();
}
} // namespace OpenGL

View file

@ -1,28 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_base.h"
namespace OpenGL {
class Bicubic : public TextureFilterBase {
public:
static constexpr std::string_view NAME = "Bicubic";
explicit Bicubic(u16 scale_factor);
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
OpenGLState state{};
OGLProgram program{};
OGLVertexArray vao{};
OGLSampler src_sampler{};
};
} // namespace OpenGL

View file

@ -1,47 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_opengl/texture_filters/nearest_neighbor/nearest_neighbor.h"
#include "shaders/nearest_neighbor.frag"
#include "shaders/tex_coord.vert"
namespace OpenGL {
NearestNeighbor::NearestNeighbor(u16 scale_factor) : TextureFilterBase(scale_factor) {
program.Create(tex_coord_vert.data(), nearest_neighbor_frag.data());
vao.Create();
src_sampler.Create();
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.draw.shader_program = program.handle;
state.texture_units[0].sampler = src_sampler.handle;
// linear minification still makes sense
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void NearestNeighbor::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex.handle,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
cur_state.Apply();
}
} // namespace OpenGL

View file

@ -1,12 +0,0 @@
//? #version 330
precision mediump float;
in vec2 tex_coord;
out vec4 frag_color;
uniform sampler2D input_texture;
void main() {
frag_color = texture(input_texture, tex_coord);
}

View file

@ -1,27 +0,0 @@
// Copyright 2022 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_base.h"
namespace OpenGL {
class NearestNeighbor : public TextureFilterBase {
public:
static constexpr std::string_view NAME = "Nearest Neighbor";
explicit NearestNeighbor(u16 scale_factor);
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
OpenGLState state{};
OGLProgram program{};
OGLVertexArray vao{};
OGLSampler src_sampler{};
};
} // namespace OpenGL

View file

@ -1,46 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/renderer_opengl/texture_filters/scale_force/scale_force.h"
#include "shaders/scale_force.frag"
#include "shaders/tex_coord.vert"
namespace OpenGL {
ScaleForce::ScaleForce(u16 scale_factor) : TextureFilterBase(scale_factor) {
program.Create(tex_coord_vert.data(), scale_force_frag.data());
vao.Create();
src_sampler.Create();
state.draw.shader_program = program.handle;
state.draw.vertex_array = vao.handle;
state.draw.shader_program = program.handle;
state.texture_units[0].sampler = src_sampler.handle;
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glSamplerParameteri(src_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void ScaleForce::Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) {
const OpenGLState cur_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = src_tex.handle;
state.draw.draw_framebuffer = draw_fbo.handle;
state.viewport = {static_cast<GLint>(dst_rect.left), static_cast<GLint>(dst_rect.bottom),
static_cast<GLsizei>(dst_rect.GetWidth()),
static_cast<GLsizei>(dst_rect.GetHeight())};
state.Apply();
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, dst_tex.handle,
0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
cur_state.Apply();
}
} // namespace OpenGL

View file

@ -1,28 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_state.h"
#include "video_core/renderer_opengl/texture_filters/texture_filter_base.h"
namespace OpenGL {
class ScaleForce : public TextureFilterBase {
public:
static constexpr std::string_view NAME = "ScaleForce";
explicit ScaleForce(u16 scale_factor);
void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) override;
private:
OpenGLState state{};
OGLProgram program{};
OGLVertexArray vao{};
OGLSampler src_sampler{};
};
} // namespace OpenGL

View file

@ -1,35 +0,0 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string_view>
#include "common/common_types.h"
#include "common/math_util.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
namespace OpenGL {
class TextureRuntime;
class OGLTexture;
class TextureFilterBase {
friend class TextureFilterer;
public:
explicit TextureFilterBase(u16 scale_factor) : scale_factor(scale_factor) {
draw_fbo.Create();
};
virtual ~TextureFilterBase() = default;
private:
virtual void Filter(const OGLTexture& src_tex, Common::Rectangle<u32> src_rect,
const OGLTexture& dst_tex, Common::Rectangle<u32> dst_rect) = 0;
protected:
OGLFramebuffer draw_fbo;
const u16 scale_factor{};
};
} // namespace OpenGL

Some files were not shown because too many files have changed in this diff Show more