Add 3D MSAA and scaling support to GLES3
This commit is contained in:
parent
d76c1d0e51
commit
caddce14dd
13 changed files with 832 additions and 55 deletions
|
@ -2778,9 +2778,11 @@
|
|||
</member>
|
||||
<member name="xr/openxr/foveation_dynamic" type="bool" setter="" getter="" default="false">
|
||||
If true and foveation is supported, will automatically adjust foveation level based on framerate up to the level set on [member xr/openxr/foveation_level].
|
||||
[b]Note:[/b] Only works on compatibility renderer.
|
||||
</member>
|
||||
<member name="xr/openxr/foveation_level" type="int" setter="" getter="" default=""0"">
|
||||
Applied foveation level if supported: 0 = off, 1 = low, 2 = medium, 3 = high.
|
||||
[b]Note:[/b] Only works on compatibility renderer.
|
||||
</member>
|
||||
<member name="xr/openxr/reference_space" type="int" setter="" getter="" default=""1"">
|
||||
Specify the default reference space.
|
||||
|
|
|
@ -2238,9 +2238,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
|
|||
|
||||
bool fb_cleared = false;
|
||||
|
||||
Size2i screen_size;
|
||||
screen_size.x = rb->width;
|
||||
screen_size.y = rb->height;
|
||||
Size2i screen_size = rb->internal_size;
|
||||
|
||||
bool use_wireframe = get_debug_draw_mode() == RS::VIEWPORT_DEBUG_DRAW_WIREFRAME;
|
||||
|
||||
|
@ -2360,8 +2358,10 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
|
|||
}
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo);
|
||||
glViewport(0, 0, rb->width, rb->height);
|
||||
GLuint fbo = rb->get_render_fbo();
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glViewport(0, 0, rb->internal_size.x, rb->internal_size.y);
|
||||
|
||||
glCullFace(GL_BACK);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
@ -2463,25 +2463,48 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
|
|||
}
|
||||
|
||||
if (scene_state.used_screen_texture || scene_state.used_depth_texture) {
|
||||
texture_storage->copy_scene_to_backbuffer(rt, scene_state.used_screen_texture, scene_state.used_depth_texture);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, rt->fbo);
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rt->backbuffer_fbo);
|
||||
if (scene_state.used_screen_texture) {
|
||||
glBlitFramebuffer(0, 0, rt->size.x, rt->size.y,
|
||||
0, 0, rt->size.x, rt->size.y,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 5);
|
||||
glBindTexture(GL_TEXTURE_2D, rt->backbuffer);
|
||||
Size2i size;
|
||||
GLuint backbuffer_fbo = 0;
|
||||
GLuint backbuffer = 0;
|
||||
GLuint backbuffer_depth = 0;
|
||||
|
||||
if (rb->get_scaling_3d_mode() == RS::VIEWPORT_SCALING_3D_MODE_OFF) {
|
||||
texture_storage->check_backbuffer(rt, scene_state.used_screen_texture, scene_state.used_depth_texture); // note, badly names, this just allocates!
|
||||
|
||||
size = rt->size;
|
||||
backbuffer_fbo = rt->backbuffer_fbo;
|
||||
backbuffer = rt->backbuffer;
|
||||
backbuffer_depth = rt->backbuffer_depth;
|
||||
} else {
|
||||
rb->check_backbuffer(scene_state.used_screen_texture, scene_state.used_depth_texture);
|
||||
size = rb->get_internal_size();
|
||||
backbuffer_fbo = rb->get_backbuffer_fbo();
|
||||
backbuffer = rb->get_backbuffer();
|
||||
backbuffer_depth = rb->get_backbuffer_depth();
|
||||
}
|
||||
if (scene_state.used_depth_texture) {
|
||||
glBlitFramebuffer(0, 0, rt->size.x, rt->size.y,
|
||||
0, 0, rt->size.x, rt->size.y,
|
||||
GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 6);
|
||||
glBindTexture(GL_TEXTURE_2D, rt->backbuffer_depth);
|
||||
|
||||
if (backbuffer_fbo != 0) {
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, backbuffer_fbo);
|
||||
if (scene_state.used_screen_texture) {
|
||||
glBlitFramebuffer(0, 0, size.x, size.y,
|
||||
0, 0, size.x, size.y,
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 5);
|
||||
glBindTexture(GL_TEXTURE_2D, backbuffer);
|
||||
}
|
||||
if (scene_state.used_depth_texture) {
|
||||
glBlitFramebuffer(0, 0, size.x, size.y,
|
||||
0, 0, size.x, size.y,
|
||||
GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 6);
|
||||
glBindTexture(GL_TEXTURE_2D, backbuffer_depth);
|
||||
}
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo);
|
||||
|
||||
// Bound framebuffer may have changed, so change it back
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
}
|
||||
|
||||
RENDER_TIMESTAMP("Render 3D Transparent Pass");
|
||||
|
@ -2498,14 +2521,110 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
|
|||
}
|
||||
|
||||
if (rb.is_valid()) {
|
||||
_render_buffers_debug_draw(rb, p_shadow_atlas);
|
||||
_render_buffers_debug_draw(rb, p_shadow_atlas, fbo);
|
||||
}
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
_render_post_processing(&render_data);
|
||||
|
||||
texture_storage->render_target_disable_clear_request(rb->render_target);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_render_data) {
|
||||
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
|
||||
Ref<RenderSceneBuffersGLES3> rb = p_render_data->render_buffers;
|
||||
ERR_FAIL_COND(rb.is_null());
|
||||
|
||||
RID render_target = rb->get_render_target();
|
||||
Size2i internal_size = rb->get_internal_size();
|
||||
Size2i target_size = rb->get_target_size();
|
||||
uint32_t view_count = rb->get_view_count();
|
||||
|
||||
// bool msaa2d_needs_resolve = texture_storage->render_target_get_msaa(render_target) != RS::VIEWPORT_MSAA_DISABLED && !GLES3::Config::get_singleton()->rt_msaa_supported;
|
||||
bool msaa3d_needs_resolve = rb->get_msaa_needs_resolve();
|
||||
GLuint fbo_msaa_3d = rb->get_msaa3d_fbo();
|
||||
GLuint fbo_int = rb->get_internal_fbo();
|
||||
GLuint fbo_rt = texture_storage->render_target_get_fbo(render_target); // TODO if MSAA 2D is enabled and we're not using rt_msaa, get 2D render target here.
|
||||
|
||||
if (view_count == 1) {
|
||||
// Resolve if needed.
|
||||
if (fbo_msaa_3d != 0 && msaa3d_needs_resolve) {
|
||||
// We can use blit to copy things over
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_msaa_3d);
|
||||
|
||||
if (fbo_int != 0) {
|
||||
// We can't combine resolve and scaling, so resolve into our internal buffer
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_int);
|
||||
} else {
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_rt);
|
||||
}
|
||||
glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, internal_size.x, internal_size.y, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
|
||||
if (fbo_int != 0) {
|
||||
// TODO If we have glow or other post processing, we upscale only depth here, post processing will also do scaling.
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_int);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_rt);
|
||||
glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_rt);
|
||||
} else if ((fbo_msaa_3d != 0 && msaa3d_needs_resolve) || (fbo_int != 0)) {
|
||||
// TODO investigate if it's smarter to cache these FBOs
|
||||
GLuint fbos[2]; // read and write
|
||||
glGenFramebuffers(2, fbos);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
|
||||
|
||||
if (fbo_msaa_3d != 0 && msaa3d_needs_resolve) {
|
||||
GLuint read_color = rb->get_msaa3d_color();
|
||||
GLuint read_depth = rb->get_msaa3d_depth();
|
||||
GLuint write_color = 0;
|
||||
GLuint write_depth = 0;
|
||||
|
||||
if (fbo_int != 0) {
|
||||
write_color = rb->get_internal_color();
|
||||
write_depth = rb->get_internal_depth();
|
||||
} else {
|
||||
write_color = texture_storage->render_target_get_color(render_target);
|
||||
write_depth = texture_storage->render_target_get_depth(render_target);
|
||||
}
|
||||
|
||||
for (uint32_t v = 0; v < view_count; v++) {
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, read_color, 0, v);
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, read_depth, 0, v);
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, write_color, 0, v);
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, write_depth, 0, v);
|
||||
glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, internal_size.x, internal_size.y, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
if (fbo_int != 0) {
|
||||
GLuint read_color = rb->get_internal_color();
|
||||
GLuint read_depth = rb->get_internal_depth();
|
||||
GLuint write_color = texture_storage->render_target_get_color(render_target);
|
||||
GLuint write_depth = texture_storage->render_target_get_depth(render_target);
|
||||
|
||||
for (uint32_t v = 0; v < view_count; v++) {
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, read_color, 0, v);
|
||||
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, read_depth, 0, v);
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, write_color, 0, v);
|
||||
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, write_depth, 0, v);
|
||||
|
||||
glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_COLOR_BUFFER_BIT, GL_LINEAR);
|
||||
glBlitFramebuffer(0, 0, internal_size.x, internal_size.y, 0, 0, target_size.x, target_size.y, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo_rt);
|
||||
glDeleteFramebuffers(2, fbos);
|
||||
}
|
||||
}
|
||||
|
||||
template <PassMode p_pass_mode>
|
||||
void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params, const RenderDataGLES3 *p_render_data, uint32_t p_from_element, uint32_t p_to_element, bool p_alpha_pass) {
|
||||
GLES3::MeshStorage *mesh_storage = GLES3::MeshStorage::get_singleton();
|
||||
|
@ -3123,7 +3242,7 @@ Ref<RenderSceneBuffers> RasterizerSceneGLES3::render_buffers_create() {
|
|||
return rb;
|
||||
}
|
||||
|
||||
void RasterizerSceneGLES3::_render_buffers_debug_draw(Ref<RenderSceneBuffersGLES3> p_render_buffers, RID p_shadow_atlas) {
|
||||
void RasterizerSceneGLES3::_render_buffers_debug_draw(Ref<RenderSceneBuffersGLES3> p_render_buffers, RID p_shadow_atlas, GLuint p_fbo) {
|
||||
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
|
||||
GLES3::LightStorage *light_storage = GLES3::LightStorage::get_singleton();
|
||||
GLES3::CopyEffects *copy_effects = GLES3::CopyEffects::get_singleton();
|
||||
|
@ -3200,8 +3319,11 @@ void RasterizerSceneGLES3::_render_buffers_debug_draw(Ref<RenderSceneBuffersGLES
|
|||
}
|
||||
}
|
||||
}
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, rt->fbo);
|
||||
glViewport(0, 0, rt->size.width, rt->size.height);
|
||||
|
||||
// Set back to FBO
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, p_fbo);
|
||||
Size2i size = p_render_buffers->get_internal_size();
|
||||
glViewport(0, 0, size.width, size.height);
|
||||
glBindTexture(GL_TEXTURE_2D, shadow_atlas_texture);
|
||||
|
||||
copy_effects->copy_to_rect(Rect2(Vector2(), Vector2(0.5, 0.5)));
|
||||
|
|
|
@ -518,6 +518,7 @@ private:
|
|||
void _fill_render_list(RenderListType p_render_list, const RenderDataGLES3 *p_render_data, PassMode p_pass_mode, bool p_append = false);
|
||||
void _render_shadows(const RenderDataGLES3 *p_render_data, const Size2i &p_viewport_size = Size2i(1, 1));
|
||||
void _render_shadow_pass(RID p_light, RID p_shadow_atlas, int p_pass, const PagedArray<RenderGeometryInstance *> &p_instances, const Plane &p_camera_plane = Plane(), float p_lod_distance_multiplier = 0, float p_screen_mesh_lod_threshold = 0.0, RenderingMethod::RenderInfo *p_render_info = nullptr, const Size2i &p_viewport_size = Size2i(1, 1));
|
||||
void _render_post_processing(const RenderDataGLES3 *p_render_data);
|
||||
|
||||
template <PassMode p_pass_mode>
|
||||
_FORCE_INLINE_ void _render_list_template(RenderListParameters *p_params, const RenderDataGLES3 *p_render_data, uint32_t p_from_element, uint32_t p_to_element, bool p_alpha_pass = false);
|
||||
|
@ -530,7 +531,7 @@ protected:
|
|||
float screen_space_roughness_limiter_amount = 0.25;
|
||||
float screen_space_roughness_limiter_limit = 0.18;
|
||||
|
||||
void _render_buffers_debug_draw(Ref<RenderSceneBuffersGLES3> p_render_buffers, RID p_shadow_atlas);
|
||||
void _render_buffers_debug_draw(Ref<RenderSceneBuffersGLES3> p_render_buffers, RID p_shadow_atlas, GLuint p_fbo);
|
||||
|
||||
/* Camera Attributes */
|
||||
|
||||
|
|
|
@ -92,14 +92,49 @@ Config::Config() {
|
|||
anisotropic_level = MIN(float(1 << int(GLOBAL_GET("rendering/textures/default_filters/anisotropic_filtering_level"))), anisotropic_level);
|
||||
}
|
||||
|
||||
glGetIntegerv(GL_MAX_SAMPLES, &msaa_max_samples);
|
||||
#ifdef WEB_ENABLED
|
||||
msaa_supported = (msaa_max_samples > 0);
|
||||
#else
|
||||
msaa_supported = extensions.has("GL_EXT_framebuffer_multisample");
|
||||
#endif
|
||||
#ifndef IOS_ENABLED
|
||||
msaa_multiview_supported = extensions.has("GL_EXT_multiview_texture_multisample");
|
||||
multiview_supported = extensions.has("GL_OVR_multiview2") || extensions.has("GL_OVR_multiview");
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
// These are GLES only
|
||||
rt_msaa_supported = extensions.has("GL_EXT_multisampled_render_to_texture");
|
||||
rt_msaa_multiview_supported = extensions.has("GL_OVR_multiview_multisampled_render_to_texture");
|
||||
|
||||
if (multiview_supported) {
|
||||
eglFramebufferTextureMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)eglGetProcAddress("glFramebufferTextureMultiviewOVR");
|
||||
if (eglFramebufferTextureMultiviewOVR == nullptr) {
|
||||
multiview_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (msaa_multiview_supported) {
|
||||
eglTexStorage3DMultisample = (PFNGLTEXSTORAGE3DMULTISAMPLEPROC)eglGetProcAddress("glTexStorage3DMultisample");
|
||||
if (eglTexStorage3DMultisample == nullptr) {
|
||||
msaa_multiview_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rt_msaa_supported) {
|
||||
eglFramebufferTexture2DMultisampleEXT = (PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)eglGetProcAddress("glFramebufferTexture2DMultisampleEXT");
|
||||
if (eglFramebufferTexture2DMultisampleEXT == nullptr) {
|
||||
rt_msaa_supported = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (rt_msaa_multiview_supported) {
|
||||
eglFramebufferTextureMultisampleMultiviewOVR = (PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)eglGetProcAddress("glFramebufferTextureMultisampleMultiviewOVR");
|
||||
if (eglFramebufferTextureMultisampleMultiviewOVR == nullptr) {
|
||||
rt_msaa_multiview_supported = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
force_vertex_shading = false; //GLOBAL_GET("rendering/quality/shading/force_vertex_shading");
|
||||
|
|
|
@ -42,6 +42,9 @@
|
|||
|
||||
#ifdef ANDROID_ENABLED
|
||||
typedef void (*PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC)(GLenum, GLenum, GLuint, GLint, GLint, GLsizei);
|
||||
typedef void (*PFNGLTEXSTORAGE3DMULTISAMPLEPROC)(GLenum, GLsizei, GLenum, GLsizei, GLsizei, GLsizei, GLboolean);
|
||||
typedef void (*PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC)(GLenum, GLenum, GLenum, GLuint, GLint, GLsizei);
|
||||
typedef void (*PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC)(GLenum, GLenum, GLuint, GLint, GLsizei, GLint, GLsizei);
|
||||
#endif
|
||||
|
||||
namespace GLES3 {
|
||||
|
@ -82,9 +85,18 @@ public:
|
|||
bool support_anisotropic_filter = false;
|
||||
float anisotropic_level = 0.0f;
|
||||
|
||||
GLint msaa_max_samples = 0;
|
||||
bool msaa_supported = false;
|
||||
bool msaa_multiview_supported = false;
|
||||
bool rt_msaa_supported = false;
|
||||
bool rt_msaa_multiview_supported = false;
|
||||
bool multiview_supported = false;
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
PFNGLFRAMEBUFFERTEXTUREMULTIVIEWOVRPROC eglFramebufferTextureMultiviewOVR = nullptr;
|
||||
PFNGLTEXSTORAGE3DMULTISAMPLEPROC eglTexStorage3DMultisample = nullptr;
|
||||
PFNGLFRAMEBUFFERTEXTURE2DMULTISAMPLEEXTPROC eglFramebufferTexture2DMultisampleEXT = nullptr;
|
||||
PFNGLFRAMEBUFFERTEXTUREMULTISAMPLEMULTIVIEWOVRPROC eglFramebufferTextureMultisampleMultiviewOVR = nullptr;
|
||||
#endif
|
||||
|
||||
static Config *get_singleton() { return singleton; };
|
||||
|
|
|
@ -3000,10 +3000,6 @@ void SceneShaderData::set_code(const String &p_code) {
|
|||
WARN_PRINT_ONCE_ED("Transmittance is only available when using the Forward+ rendering backend.");
|
||||
}
|
||||
|
||||
if (uses_depth_texture) {
|
||||
WARN_PRINT_ONCE_ED("Reading from the depth texture is not supported when using the GL Compatibility backend yet. Support will be added in a future release.");
|
||||
}
|
||||
|
||||
if (uses_normal_texture) {
|
||||
WARN_PRINT_ONCE_ED("Reading from the normal-roughness texture is only available when using the Forward+ or Mobile rendering backends.");
|
||||
}
|
||||
|
|
|
@ -31,30 +31,508 @@
|
|||
#ifdef GLES3_ENABLED
|
||||
|
||||
#include "render_scene_buffers_gles3.h"
|
||||
#include "config.h"
|
||||
#include "texture_storage.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
#define glFramebufferTextureMultiviewOVR GLES3::Config::get_singleton()->eglFramebufferTextureMultiviewOVR
|
||||
#define glTexStorage3DMultisample GLES3::Config::get_singleton()->eglTexStorage3DMultisample
|
||||
#define glFramebufferTexture2DMultisampleEXT GLES3::Config::get_singleton()->eglFramebufferTexture2DMultisampleEXT
|
||||
#define glFramebufferTextureMultisampleMultiviewOVR GLES3::Config::get_singleton()->eglFramebufferTextureMultisampleMultiviewOVR
|
||||
#endif // ANDROID_ENABLED
|
||||
|
||||
// Will only be defined if GLES 3.2 headers are included
|
||||
#ifndef GL_TEXTURE_2D_MULTISAMPLE_ARRAY
|
||||
#define GL_TEXTURE_2D_MULTISAMPLE_ARRAY 0x9102
|
||||
#endif
|
||||
|
||||
RenderSceneBuffersGLES3::~RenderSceneBuffersGLES3() {
|
||||
free_render_buffer_data();
|
||||
}
|
||||
|
||||
GLuint RenderSceneBuffersGLES3::_rt_get_cached_fbo(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count) {
|
||||
FBDEF new_fbo;
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
// There shouldn't be more then 3 entries in this...
|
||||
for (const FBDEF &cached_fbo : msaa3d.cached_fbos) {
|
||||
if (cached_fbo.color == p_color && cached_fbo.depth == p_depth) {
|
||||
return cached_fbo.fbo;
|
||||
}
|
||||
}
|
||||
|
||||
new_fbo.color = p_color;
|
||||
new_fbo.depth = p_depth;
|
||||
|
||||
glGenFramebuffers(1, &new_fbo.fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, new_fbo.fbo);
|
||||
|
||||
if (p_view_count > 1) {
|
||||
glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, p_color, 0, p_samples, 0, p_view_count);
|
||||
glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, p_depth, 0, p_samples, 0, p_view_count);
|
||||
} else {
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, p_color, 0, p_samples);
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, p_depth, 0, p_samples);
|
||||
}
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
WARN_PRINT("Could not create 3D MSAA framebuffer, status: " + GLES3::TextureStorage::get_singleton()->get_framebuffer_error(status));
|
||||
|
||||
glDeleteFramebuffers(1, &new_fbo.fbo);
|
||||
|
||||
new_fbo.fbo = 0;
|
||||
} else {
|
||||
// cache it!
|
||||
msaa3d.cached_fbos.push_back(new_fbo);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
#endif
|
||||
|
||||
return new_fbo.fbo;
|
||||
}
|
||||
|
||||
void RenderSceneBuffersGLES3::configure(const RenderSceneBuffersConfiguration *p_config) {
|
||||
//internal_size.x = p_config->get_internal_size().x; // ignore for now
|
||||
//internal_size.y = p_config->get_internal_size().y;
|
||||
width = p_config->get_target_size().x;
|
||||
height = p_config->get_target_size().y;
|
||||
//scaling_3d_mode = p_config->get_scaling_3d_mode()
|
||||
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
|
||||
GLES3::Config *config = GLES3::Config::get_singleton();
|
||||
|
||||
free_render_buffer_data();
|
||||
|
||||
internal_size = p_config->get_internal_size();
|
||||
target_size = p_config->get_target_size();
|
||||
scaling_3d_mode = p_config->get_scaling_3d_mode();
|
||||
//fsr_sharpness = p_config->get_fsr_sharpness();
|
||||
//texture_mipmap_bias = p_config->get_texture_mipmap_bias();
|
||||
render_target = p_config->get_render_target();
|
||||
//msaa = p_config->get_msaa_3d();
|
||||
msaa3d.mode = p_config->get_msaa_3d();
|
||||
//screen_space_aa = p_config->get_screen_space_aa();
|
||||
//use_debanding = p_config->get_use_debanding();
|
||||
view_count = p_config->get_view_count();
|
||||
view_count = config->multiview_supported ? p_config->get_view_count() : 1;
|
||||
|
||||
free_render_buffer_data();
|
||||
ERR_FAIL_COND(view_count == 0);
|
||||
bool use_multiview = view_count > 1;
|
||||
|
||||
// Check our scaling mode
|
||||
if (scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF && internal_size.x == 0 && internal_size.y == 0) {
|
||||
// Disable, no size set.
|
||||
scaling_3d_mode = RS::VIEWPORT_SCALING_3D_MODE_OFF;
|
||||
} else if (scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF && internal_size == target_size) {
|
||||
// If size matches, we won't use scaling.
|
||||
scaling_3d_mode = RS::VIEWPORT_SCALING_3D_MODE_OFF;
|
||||
} else if (scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF && scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_BILINEAR) {
|
||||
// We only support bilinear scaling atm.
|
||||
WARN_PRINT_ONCE("GLES only supports bilinear scaling.");
|
||||
scaling_3d_mode = RS::VIEWPORT_SCALING_3D_MODE_BILINEAR;
|
||||
}
|
||||
|
||||
bool use_internal_buffer = scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF; // TODO also need this if doing post processing like glow
|
||||
if (use_internal_buffer) {
|
||||
// Setup our internal buffer.
|
||||
bool is_transparent = texture_storage->render_target_get_transparent(render_target);
|
||||
GLuint color_internal_format = is_transparent ? GL_RGBA8 : GL_RGB10_A2;
|
||||
GLuint color_format = GL_RGBA;
|
||||
GLuint color_type = is_transparent ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
|
||||
GLenum texture_target = use_multiview ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
|
||||
|
||||
// Create our color buffer.
|
||||
glGenTextures(1, &internal3d.color);
|
||||
glBindTexture(texture_target, internal3d.color);
|
||||
|
||||
if (use_multiview) {
|
||||
glTexImage3D(texture_target, 0, color_internal_format, internal_size.x, internal_size.y, view_count, 0, color_format, color_type, nullptr);
|
||||
} else {
|
||||
glTexImage2D(texture_target, 0, color_internal_format, internal_size.x, internal_size.y, 0, color_format, color_type, nullptr);
|
||||
}
|
||||
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
GLES3::Utilities::get_singleton()->texture_allocated_data(internal3d.color, internal_size.x * internal_size.y * view_count * 4, "3D color texture");
|
||||
|
||||
// Create our depth buffer.
|
||||
glGenTextures(1, &internal3d.depth);
|
||||
glBindTexture(texture_target, internal3d.depth);
|
||||
|
||||
if (use_multiview) {
|
||||
glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
|
||||
} else {
|
||||
glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
|
||||
}
|
||||
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
GLES3::Utilities::get_singleton()->texture_allocated_data(internal3d.depth, internal_size.x * internal_size.y * view_count * 3, "3D depth texture");
|
||||
|
||||
// Create our internal 3D FBO.
|
||||
// Note that if MSAA is used and our rt_msaa_* extensions are available, this is only used for blitting and effects.
|
||||
glGenFramebuffers(1, &internal3d.fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, internal3d.fbo);
|
||||
|
||||
#ifndef IOS_ENABLED
|
||||
if (use_multiview) {
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, internal3d.color, 0, 0, view_count);
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, internal3d.depth, 0, 0, view_count);
|
||||
} else {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, internal3d.color, 0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_target, internal3d.depth, 0);
|
||||
}
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
_clear_intermediate_buffers();
|
||||
WARN_PRINT("Could not create 3D buffers, status: " + texture_storage->get_framebuffer_error(status));
|
||||
}
|
||||
|
||||
glBindTexture(texture_target, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
// Check if we support MSAA.
|
||||
if (msaa3d.mode != RS::VIEWPORT_MSAA_DISABLED && internal_size.x == 0 && internal_size.y == 0) {
|
||||
// Disable, no size set.
|
||||
msaa3d.mode = RS::VIEWPORT_MSAA_DISABLED;
|
||||
} else if (!use_multiview && msaa3d.mode != RS::VIEWPORT_MSAA_DISABLED && !config->msaa_supported && !config->rt_msaa_supported) {
|
||||
WARN_PRINT_ONCE("MSAA is not supported on this device.");
|
||||
msaa3d.mode = RS::VIEWPORT_MSAA_DISABLED;
|
||||
} else if (use_multiview && msaa3d.mode != RS::VIEWPORT_MSAA_DISABLED && !config->msaa_multiview_supported && !config->rt_msaa_multiview_supported) {
|
||||
WARN_PRINT_ONCE("Multiview MSAA is not supported on this device.");
|
||||
msaa3d.mode = RS::VIEWPORT_MSAA_DISABLED;
|
||||
}
|
||||
|
||||
if (msaa3d.mode != RS::VIEWPORT_MSAA_DISABLED) {
|
||||
// Setup MSAA.
|
||||
const GLsizei samples[] = { 1, 2, 4, 8 };
|
||||
msaa3d.samples = samples[msaa3d.mode];
|
||||
|
||||
// Constrain by limits of OpenGL driver.
|
||||
if (msaa3d.samples > config->msaa_max_samples) {
|
||||
msaa3d.samples = config->msaa_max_samples;
|
||||
}
|
||||
|
||||
if (!use_multiview && !config->rt_msaa_supported) {
|
||||
// Render to texture extensions not supported? fall back to MSAA framebuffer through GL_EXT_framebuffer_multisample.
|
||||
// Note, if 2D MSAA matches 3D MSAA and we're not scaling, it would be ideal if we reuse our 2D MSAA buffer here.
|
||||
// We can't however because we don't trigger a change in configuration if 2D MSAA changes.
|
||||
// We'll accept the overhead in this situation.
|
||||
|
||||
msaa3d.needs_resolve = true;
|
||||
msaa3d.check_fbo_cache = false;
|
||||
|
||||
bool is_transparent = texture_storage->render_target_get_transparent(render_target);
|
||||
GLuint color_internal_format = is_transparent ? GL_RGBA8 : GL_RGB10_A2;
|
||||
|
||||
// Create our color buffer.
|
||||
glGenRenderbuffers(1, &msaa3d.color);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, msaa3d.color);
|
||||
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa3d.samples, color_internal_format, internal_size.x, internal_size.y);
|
||||
GLES3::Utilities::get_singleton()->render_buffer_allocated_data(msaa3d.color, internal_size.x * internal_size.y * view_count * 4 * msaa3d.samples, "MSAA 3D color render buffer");
|
||||
|
||||
// Create our depth buffer.
|
||||
glGenRenderbuffers(1, &msaa3d.depth);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, msaa3d.depth);
|
||||
|
||||
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y);
|
||||
GLES3::Utilities::get_singleton()->render_buffer_allocated_data(msaa3d.depth, internal_size.x * internal_size.y * view_count * 3 * msaa3d.samples, "MSAA 3D depth render buffer");
|
||||
|
||||
// Create our MSAA 3D FBO.
|
||||
glGenFramebuffers(1, &msaa3d.fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, msaa3d.fbo);
|
||||
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msaa3d.color);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msaa3d.depth);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
_clear_msaa3d_buffers();
|
||||
WARN_PRINT("Could not create 3D MSAA buffers, status: " + texture_storage->get_framebuffer_error(status));
|
||||
}
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
#if !defined(IOS_ENABLED) && !defined(WEB_ENABLED)
|
||||
} else if (use_multiview && !config->rt_msaa_multiview_supported) {
|
||||
// Render to texture extensions not supported? fall back to MSAA textures through GL_EXT_multiview_texture_multisample.
|
||||
msaa3d.needs_resolve = true;
|
||||
msaa3d.check_fbo_cache = false;
|
||||
|
||||
bool is_transparent = texture_storage->render_target_get_transparent(render_target);
|
||||
GLuint color_internal_format = is_transparent ? GL_RGBA8 : GL_RGB10_A2;
|
||||
|
||||
// Create our color buffer.
|
||||
glGenTextures(1, &msaa3d.color);
|
||||
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.color);
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, color_internal_format, internal_size.x, internal_size.y, view_count, GL_TRUE);
|
||||
#else
|
||||
glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, color_internal_format, internal_size.x, internal_size.y, view_count, GL_TRUE);
|
||||
#endif
|
||||
|
||||
GLES3::Utilities::get_singleton()->texture_allocated_data(msaa3d.color, internal_size.x * internal_size.y * view_count * 4 * msaa3d.samples, "MSAA 3D color texture");
|
||||
|
||||
// Create our depth buffer.
|
||||
glGenTextures(1, &msaa3d.depth);
|
||||
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.depth);
|
||||
|
||||
#ifdef ANDROID_ENABLED
|
||||
glTexStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, GL_TRUE);
|
||||
#else
|
||||
glTexImage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, msaa3d.samples, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, GL_TRUE);
|
||||
#endif
|
||||
|
||||
GLES3::Utilities::get_singleton()->texture_allocated_data(msaa3d.depth, internal_size.x * internal_size.y * view_count * msaa3d.samples, "MSAA 3D depth texture");
|
||||
|
||||
// Create our MSAA 3D FBO.
|
||||
glGenFramebuffers(1, &msaa3d.fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, msaa3d.fbo);
|
||||
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, msaa3d.color, 0, 0, view_count);
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, msaa3d.depth, 0, 0, view_count);
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
_clear_msaa3d_buffers();
|
||||
WARN_PRINT("Could not create 3D MSAA buffers, status: " + texture_storage->get_framebuffer_error(status));
|
||||
}
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
#endif
|
||||
#if defined(ANDROID_ENABLED) || defined(WEB_ENABLED) // Only supported on OpenGLES!
|
||||
} else if (!use_internal_buffer) {
|
||||
// We are going to render directly into our render target textures,
|
||||
// these can change from frame to frame as we cycle through swapchains,
|
||||
// hence we'll use our FBO cache here.
|
||||
msaa3d.needs_resolve = false;
|
||||
msaa3d.check_fbo_cache = true;
|
||||
#endif
|
||||
#ifdef ANDROID_ENABLED
|
||||
} else if (use_internal_buffer) {
|
||||
// We can combine MSAA and scaling/effects.
|
||||
msaa3d.needs_resolve = false;
|
||||
msaa3d.check_fbo_cache = false;
|
||||
|
||||
// We render to our internal textures, MSAA is only done in tile memory only.
|
||||
// On mobile this means MSAA never leaves tile memory = efficiency!
|
||||
glGenFramebuffers(1, &msaa3d.fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, msaa3d.fbo);
|
||||
|
||||
if (use_multiview) {
|
||||
glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, internal3d.color, 0, msaa3d.samples, 0, view_count);
|
||||
glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, internal3d.depth, 0, msaa3d.samples, 0, view_count);
|
||||
} else {
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, internal3d.color, 0, msaa3d.samples);
|
||||
glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, internal3d.depth, 0, msaa3d.samples);
|
||||
}
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
_clear_msaa3d_buffers();
|
||||
WARN_PRINT("Could not create 3D MSAA framebuffer, status: " + texture_storage->get_framebuffer_error(status));
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
#endif
|
||||
} else {
|
||||
// HUH? how did we get here?
|
||||
WARN_PRINT_ONCE("MSAA is not supported on this device.");
|
||||
msaa3d.mode = RS::VIEWPORT_MSAA_DISABLED;
|
||||
msaa3d.samples = 1;
|
||||
msaa3d.check_fbo_cache = false;
|
||||
}
|
||||
} else {
|
||||
msaa3d.samples = 1;
|
||||
msaa3d.check_fbo_cache = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSceneBuffersGLES3::_clear_msaa3d_buffers() {
|
||||
for (const FBDEF &cached_fbo : msaa3d.cached_fbos) {
|
||||
GLuint fbo = cached_fbo.fbo;
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
}
|
||||
msaa3d.cached_fbos.clear();
|
||||
|
||||
if (msaa3d.fbo) {
|
||||
glDeleteFramebuffers(1, &msaa3d.fbo);
|
||||
msaa3d.fbo = 0;
|
||||
}
|
||||
|
||||
if (msaa3d.color != 0) {
|
||||
if (view_count == 1) {
|
||||
GLES3::Utilities::get_singleton()->render_buffer_free_data(msaa3d.color);
|
||||
} else {
|
||||
GLES3::Utilities::get_singleton()->texture_free_data(msaa3d.color);
|
||||
}
|
||||
msaa3d.color = 0;
|
||||
}
|
||||
|
||||
if (msaa3d.depth != 0) {
|
||||
if (view_count == 1) {
|
||||
GLES3::Utilities::get_singleton()->render_buffer_free_data(msaa3d.depth);
|
||||
} else {
|
||||
GLES3::Utilities::get_singleton()->texture_free_data(msaa3d.depth);
|
||||
}
|
||||
msaa3d.depth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSceneBuffersGLES3::_clear_intermediate_buffers() {
|
||||
if (internal3d.fbo) {
|
||||
glDeleteFramebuffers(1, &internal3d.fbo);
|
||||
internal3d.fbo = 0;
|
||||
}
|
||||
|
||||
if (internal3d.color != 0) {
|
||||
GLES3::Utilities::get_singleton()->texture_free_data(internal3d.color);
|
||||
internal3d.color = 0;
|
||||
}
|
||||
|
||||
if (internal3d.depth != 0) {
|
||||
GLES3::Utilities::get_singleton()->texture_free_data(internal3d.depth);
|
||||
internal3d.depth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSceneBuffersGLES3::check_backbuffer(bool p_need_color, bool p_need_depth) {
|
||||
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
|
||||
|
||||
// Setup our back buffer
|
||||
|
||||
if (backbuffer3d.fbo == 0) {
|
||||
glGenFramebuffers(1, &backbuffer3d.fbo);
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, backbuffer3d.fbo);
|
||||
|
||||
bool is_transparent = texture_storage->render_target_get_transparent(render_target);
|
||||
GLuint color_internal_format = is_transparent ? GL_RGBA8 : GL_RGB10_A2;
|
||||
GLuint color_format = GL_RGBA;
|
||||
GLuint color_type = is_transparent ? GL_UNSIGNED_BYTE : GL_UNSIGNED_INT_2_10_10_10_REV;
|
||||
|
||||
bool use_multiview = view_count > 1 && GLES3::Config::get_singleton()->multiview_supported;
|
||||
GLenum texture_target = use_multiview ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_2D;
|
||||
|
||||
if (backbuffer3d.color == 0 && p_need_color) {
|
||||
glGenTextures(1, &backbuffer3d.color);
|
||||
glBindTexture(texture_target, backbuffer3d.color);
|
||||
|
||||
if (use_multiview) {
|
||||
glTexImage3D(texture_target, 0, color_internal_format, internal_size.x, internal_size.y, view_count, 0, color_format, color_type, nullptr);
|
||||
} else {
|
||||
glTexImage2D(texture_target, 0, color_internal_format, internal_size.x, internal_size.y, 0, color_format, color_type, nullptr);
|
||||
}
|
||||
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
GLES3::Utilities::get_singleton()->texture_allocated_data(backbuffer3d.color, internal_size.x * internal_size.y * view_count * 4, "3D Back buffer color texture");
|
||||
|
||||
#ifndef IOS_ENABLED
|
||||
if (use_multiview) {
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, backbuffer3d.color, 0, 0, view_count);
|
||||
} else {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, backbuffer3d.color, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (backbuffer3d.depth == 0 && p_need_depth) {
|
||||
glGenTextures(1, &backbuffer3d.depth);
|
||||
glBindTexture(texture_target, backbuffer3d.depth);
|
||||
|
||||
if (use_multiview) {
|
||||
glTexImage3D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, view_count, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
|
||||
} else {
|
||||
glTexImage2D(texture_target, 0, GL_DEPTH_COMPONENT24, internal_size.x, internal_size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
|
||||
}
|
||||
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
GLES3::Utilities::get_singleton()->texture_allocated_data(backbuffer3d.depth, internal_size.x * internal_size.y * view_count * 3, "3D back buffer depth texture");
|
||||
|
||||
#ifndef IOS_ENABLED
|
||||
if (use_multiview) {
|
||||
glFramebufferTextureMultiviewOVR(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, backbuffer3d.depth, 0, 0, view_count);
|
||||
} else {
|
||||
#else
|
||||
{
|
||||
#endif
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_target, backbuffer3d.depth, 0);
|
||||
}
|
||||
}
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE) {
|
||||
_clear_back_buffers();
|
||||
WARN_PRINT("Could not create 3D back buffers, status: " + texture_storage->get_framebuffer_error(status));
|
||||
}
|
||||
|
||||
glBindTexture(texture_target, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void RenderSceneBuffersGLES3::_clear_back_buffers() {
|
||||
if (backbuffer3d.fbo) {
|
||||
glDeleteFramebuffers(1, &backbuffer3d.fbo);
|
||||
backbuffer3d.fbo = 0;
|
||||
}
|
||||
|
||||
if (backbuffer3d.color != 0) {
|
||||
GLES3::Utilities::get_singleton()->texture_free_data(backbuffer3d.color);
|
||||
backbuffer3d.color = 0;
|
||||
}
|
||||
|
||||
if (backbuffer3d.depth != 0) {
|
||||
GLES3::Utilities::get_singleton()->texture_free_data(backbuffer3d.depth);
|
||||
backbuffer3d.depth = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RenderSceneBuffersGLES3::free_render_buffer_data() {
|
||||
_clear_msaa3d_buffers();
|
||||
_clear_intermediate_buffers();
|
||||
_clear_back_buffers();
|
||||
}
|
||||
|
||||
GLuint RenderSceneBuffersGLES3::get_render_fbo() {
|
||||
if (msaa3d.check_fbo_cache) {
|
||||
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
|
||||
|
||||
GLuint color = texture_storage->render_target_get_color(render_target);
|
||||
GLuint depth = texture_storage->render_target_get_depth(render_target);
|
||||
|
||||
return _rt_get_cached_fbo(color, depth, msaa3d.samples, view_count);
|
||||
} else if (msaa3d.fbo != 0) {
|
||||
// We have an MSAA fbo, render to our MSAA buffer
|
||||
return msaa3d.fbo;
|
||||
} else if (internal3d.fbo != 0) {
|
||||
// We have an internal buffer, render to our internal buffer!
|
||||
return internal3d.fbo;
|
||||
} else {
|
||||
GLES3::TextureStorage *texture_storage = GLES3::TextureStorage::get_singleton();
|
||||
|
||||
return texture_storage->render_target_get_fbo(render_target);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
|
|
|
@ -41,21 +41,40 @@ class RenderSceneBuffersGLES3 : public RenderSceneBuffers {
|
|||
GDCLASS(RenderSceneBuffersGLES3, RenderSceneBuffers);
|
||||
|
||||
public:
|
||||
// Original implementation, need to investigate which ones we'll keep like this and what we'll change...
|
||||
|
||||
int internal_width = 0;
|
||||
int internal_height = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
Size2i internal_size; // Size of the buffer we render 3D content to.
|
||||
Size2i target_size; // Size of our output buffer (render target).
|
||||
RS::ViewportScaling3DMode scaling_3d_mode = RS::VIEWPORT_SCALING_3D_MODE_OFF;
|
||||
//float fsr_sharpness = 0.2f;
|
||||
RS::ViewportMSAA msaa = RS::VIEWPORT_MSAA_DISABLED;
|
||||
//RS::ViewportScreenSpaceAA screen_space_aa = RS::VIEWPORT_SCREEN_SPACE_AA_DISABLED;
|
||||
//bool use_taa = false;
|
||||
//bool use_debanding = false;
|
||||
uint32_t view_count = 1;
|
||||
|
||||
RID render_target;
|
||||
|
||||
//built-in textures used for ping pong image processing and blurring
|
||||
struct FBDEF {
|
||||
GLuint color = 0;
|
||||
GLuint depth = 0;
|
||||
GLuint fbo = 0;
|
||||
};
|
||||
|
||||
struct RTMSAA3D {
|
||||
RS::ViewportMSAA mode = RS::VIEWPORT_MSAA_DISABLED;
|
||||
bool needs_resolve = false;
|
||||
GLsizei samples = 1;
|
||||
GLuint color = 0;
|
||||
GLuint depth = 0;
|
||||
GLuint fbo = 0;
|
||||
|
||||
bool check_fbo_cache = false;
|
||||
Vector<FBDEF> cached_fbos;
|
||||
} msaa3d; // MSAA buffers used to render 3D
|
||||
|
||||
FBDEF internal3d; // buffers used to either render 3D (scaled/post) or to resolve MSAA into
|
||||
|
||||
FBDEF backbuffer3d; // our back buffer
|
||||
|
||||
// Built-in textures used for ping pong image processing and blurring.
|
||||
struct Blur {
|
||||
RID texture;
|
||||
|
||||
|
@ -72,6 +91,12 @@ public:
|
|||
Blur blur[2]; //the second one starts from the first mipmap
|
||||
|
||||
private:
|
||||
void _clear_msaa3d_buffers();
|
||||
void _clear_intermediate_buffers();
|
||||
void _clear_back_buffers();
|
||||
|
||||
GLuint _rt_get_cached_fbo(GLuint p_color, GLuint p_depth, GLsizei p_samples, uint32_t p_view_count);
|
||||
|
||||
public:
|
||||
virtual ~RenderSceneBuffersGLES3();
|
||||
virtual void configure(const RenderSceneBuffersConfiguration *p_config) override;
|
||||
|
@ -81,6 +106,33 @@ public:
|
|||
virtual void set_use_debanding(bool p_use_debanding) override{};
|
||||
|
||||
void free_render_buffer_data();
|
||||
|
||||
void check_backbuffer(bool p_need_color, bool p_need_depth); // check if we need to initialise our backbuffer
|
||||
|
||||
GLuint get_render_fbo();
|
||||
GLuint get_msaa3d_fbo() const { return msaa3d.fbo; }
|
||||
GLuint get_msaa3d_color() const { return msaa3d.color; }
|
||||
GLuint get_msaa3d_depth() const { return msaa3d.depth; }
|
||||
bool get_msaa_needs_resolve() const { return msaa3d.needs_resolve; }
|
||||
GLuint get_internal_fbo() const { return internal3d.fbo; }
|
||||
GLuint get_internal_color() const { return internal3d.color; }
|
||||
GLuint get_internal_depth() const { return internal3d.depth; }
|
||||
GLuint get_backbuffer_fbo() const { return backbuffer3d.fbo; }
|
||||
GLuint get_backbuffer() const { return backbuffer3d.color; }
|
||||
GLuint get_backbuffer_depth() const { return backbuffer3d.depth; }
|
||||
|
||||
// Getters
|
||||
|
||||
_FORCE_INLINE_ RID get_render_target() const { return render_target; }
|
||||
_FORCE_INLINE_ uint32_t get_view_count() const { return view_count; }
|
||||
_FORCE_INLINE_ Size2i get_internal_size() const { return internal_size; }
|
||||
_FORCE_INLINE_ Size2i get_target_size() const { return target_size; }
|
||||
_FORCE_INLINE_ RS::ViewportScaling3DMode get_scaling_3d_mode() const { return scaling_3d_mode; }
|
||||
//_FORCE_INLINE_ float get_fsr_sharpness() const { return fsr_sharpness; }
|
||||
_FORCE_INLINE_ RS::ViewportMSAA get_msaa_3d() const { return msaa3d.mode; }
|
||||
//_FORCE_INLINE_ RS::ViewportScreenSpaceAA get_screen_space_aa() const { return screen_space_aa; }
|
||||
//_FORCE_INLINE_ bool get_use_taa() const { return use_taa; }
|
||||
//_FORCE_INLINE_ bool get_use_debanding() const { return use_debanding; }
|
||||
};
|
||||
|
||||
#endif // GLES3_ENABLED
|
||||
|
|
|
@ -1732,7 +1732,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
|
|||
#else
|
||||
{
|
||||
#endif
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, rt->color, 0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, rt->color, 0);
|
||||
}
|
||||
|
||||
// depth
|
||||
|
@ -1765,7 +1765,7 @@ void TextureStorage::_update_render_target(RenderTarget *rt) {
|
|||
#else
|
||||
{
|
||||
#endif
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, rt->depth, 0);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texture_target, rt->depth, 0);
|
||||
}
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
|
@ -1874,7 +1874,7 @@ void TextureStorage::_create_render_target_backbuffer(RenderTarget *rt) {
|
|||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
}
|
||||
void GLES3::TextureStorage::copy_scene_to_backbuffer(RenderTarget *rt, const bool uses_screen_texture, const bool uses_depth_texture) {
|
||||
void GLES3::TextureStorage::check_backbuffer(RenderTarget *rt, const bool uses_screen_texture, const bool uses_depth_texture) {
|
||||
if (rt->backbuffer != 0 && rt->backbuffer_depth != 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -1935,7 +1935,7 @@ void GLES3::TextureStorage::copy_scene_to_backbuffer(RenderTarget *rt, const boo
|
|||
}
|
||||
}
|
||||
void TextureStorage::_clear_render_target(RenderTarget *rt) {
|
||||
// there is nothing to clear when DIRECT_TO_SCREEN is used
|
||||
// there is nothing else to clear when DIRECT_TO_SCREEN is used
|
||||
if (rt->direct_to_screen) {
|
||||
return;
|
||||
}
|
||||
|
@ -2229,6 +2229,7 @@ void TextureStorage::render_target_clear_used(RID p_render_target) {
|
|||
void TextureStorage::render_target_set_msaa(RID p_render_target, RS::ViewportMSAA p_msaa) {
|
||||
RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
|
||||
ERR_FAIL_NULL(rt);
|
||||
ERR_FAIL_COND(rt->direct_to_screen);
|
||||
if (p_msaa == rt->msaa) {
|
||||
return;
|
||||
}
|
||||
|
@ -2284,6 +2285,41 @@ void TextureStorage::render_target_do_clear_request(RID p_render_target) {
|
|||
glBindFramebuffer(GL_FRAMEBUFFER, system_fbo);
|
||||
}
|
||||
|
||||
GLuint TextureStorage::render_target_get_fbo(RID p_render_target) const {
|
||||
RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
|
||||
ERR_FAIL_NULL_V(rt, 0);
|
||||
|
||||
return rt->fbo;
|
||||
}
|
||||
|
||||
GLuint TextureStorage::render_target_get_color(RID p_render_target) const {
|
||||
RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
|
||||
ERR_FAIL_NULL_V(rt, 0);
|
||||
|
||||
if (rt->overridden.color.is_valid()) {
|
||||
Texture *texture = get_texture(rt->overridden.color);
|
||||
ERR_FAIL_NULL_V(texture, 0);
|
||||
|
||||
return texture->tex_id;
|
||||
} else {
|
||||
return rt->color;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint TextureStorage::render_target_get_depth(RID p_render_target) const {
|
||||
RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
|
||||
ERR_FAIL_NULL_V(rt, 0);
|
||||
|
||||
if (rt->overridden.depth.is_valid()) {
|
||||
Texture *texture = get_texture(rt->overridden.depth);
|
||||
ERR_FAIL_NULL_V(texture, 0);
|
||||
|
||||
return texture->tex_id;
|
||||
} else {
|
||||
return rt->depth;
|
||||
}
|
||||
}
|
||||
|
||||
void TextureStorage::render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) {
|
||||
RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
|
||||
ERR_FAIL_NULL(rt);
|
||||
|
|
|
@ -484,7 +484,7 @@ public:
|
|||
|
||||
/* Texture API */
|
||||
|
||||
Texture *get_texture(RID p_rid) {
|
||||
Texture *get_texture(RID p_rid) const {
|
||||
Texture *texture = texture_owner.get_or_null(p_rid);
|
||||
if (texture && texture->is_proxy) {
|
||||
return texture_owner.get_or_null(texture->proxy_to);
|
||||
|
@ -602,7 +602,7 @@ public:
|
|||
RenderTarget *get_render_target(RID p_rid) { return render_target_owner.get_or_null(p_rid); };
|
||||
bool owns_render_target(RID p_rid) { return render_target_owner.owns(p_rid); };
|
||||
|
||||
void copy_scene_to_backbuffer(RenderTarget *rt, const bool uses_screen_texture, const bool uses_depth_texture);
|
||||
void check_backbuffer(RenderTarget *rt, const bool uses_screen_texture, const bool uses_depth_texture);
|
||||
|
||||
virtual RID render_target_create() override;
|
||||
virtual void render_target_free(RID p_rid) override;
|
||||
|
@ -636,6 +636,10 @@ public:
|
|||
void render_target_disable_clear_request(RID p_render_target) override;
|
||||
void render_target_do_clear_request(RID p_render_target) override;
|
||||
|
||||
GLuint render_target_get_fbo(RID p_render_target) const;
|
||||
GLuint render_target_get_color(RID p_render_target) const;
|
||||
GLuint render_target_get_depth(RID p_render_target) const;
|
||||
|
||||
virtual void render_target_set_sdf_size_and_scale(RID p_render_target, RS::ViewportSDFOversize p_size, RS::ViewportSDFScale p_scale) override;
|
||||
virtual Rect2i render_target_get_sdf_rect(RID p_render_target) const override;
|
||||
GLuint render_target_get_sdf_texture(RID p_render_target);
|
||||
|
|
|
@ -85,6 +85,21 @@ Utilities::~Utilities() {
|
|||
}
|
||||
}
|
||||
|
||||
if (render_buffer_mem_cache) {
|
||||
uint32_t leaked_data_size = 0;
|
||||
for (const KeyValue<GLuint, ResourceAllocation> &E : render_buffer_allocs_cache) {
|
||||
#ifdef DEV_ENABLED
|
||||
ERR_PRINT(E.value.name + ": leaked " + itos(E.value.size) + " bytes.");
|
||||
#else
|
||||
ERR_PRINT("Render buffer with GL ID of " + itos(E.key) + ": leaked " + itos(E.value.size) + " bytes.");
|
||||
#endif
|
||||
leaked_data_size += E.value.size;
|
||||
}
|
||||
if (leaked_data_size < render_buffer_mem_cache) {
|
||||
ERR_PRINT("Render buffer cache is not empty. There may be an additional render buffer leak of " + itos(render_buffer_mem_cache - leaked_data_size) + " bytes.");
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer_mem_cache) {
|
||||
uint32_t leaked_data_size = 0;
|
||||
|
||||
|
@ -362,11 +377,11 @@ void Utilities::update_memory_info() {
|
|||
|
||||
uint64_t Utilities::get_rendering_info(RS::RenderingInfo p_info) {
|
||||
if (p_info == RS::RENDERING_INFO_TEXTURE_MEM_USED) {
|
||||
return texture_mem_cache;
|
||||
return texture_mem_cache + render_buffer_mem_cache; // Add render buffer memory to our texture mem.
|
||||
} else if (p_info == RS::RENDERING_INFO_BUFFER_MEM_USED) {
|
||||
return buffer_mem_cache;
|
||||
} else if (p_info == RS::RENDERING_INFO_VIDEO_MEM_USED) {
|
||||
return texture_mem_cache + buffer_mem_cache;
|
||||
return texture_mem_cache + buffer_mem_cache + render_buffer_mem_cache;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -50,9 +50,11 @@ private:
|
|||
uint32_t size = 0;
|
||||
};
|
||||
HashMap<GLuint, ResourceAllocation> buffer_allocs_cache;
|
||||
HashMap<GLuint, ResourceAllocation> render_buffer_allocs_cache;
|
||||
HashMap<GLuint, ResourceAllocation> texture_allocs_cache;
|
||||
|
||||
uint64_t buffer_mem_cache = 0;
|
||||
uint64_t render_buffer_mem_cache = 0;
|
||||
uint64_t texture_mem_cache = 0;
|
||||
|
||||
public:
|
||||
|
@ -88,6 +90,26 @@ public:
|
|||
buffer_allocs_cache.erase(p_id);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void render_buffer_allocated_data(GLuint p_id, uint32_t p_size, String p_name = "") {
|
||||
render_buffer_mem_cache += p_size;
|
||||
#ifdef DEV_ENABLED
|
||||
ERR_FAIL_COND_MSG(render_buffer_allocs_cache.has(p_id), "trying to allocate render buffer with name " + p_name + " but ID already used by " + render_buffer_allocs_cache[p_id].name);
|
||||
#endif
|
||||
ResourceAllocation resource_allocation;
|
||||
resource_allocation.size = p_size;
|
||||
#ifdef DEV_ENABLED
|
||||
resource_allocation.name = p_name + ": " + itos((uint64_t)p_id);
|
||||
#endif
|
||||
render_buffer_allocs_cache[p_id] = resource_allocation;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void render_buffer_free_data(GLuint p_id) {
|
||||
ERR_FAIL_COND(!render_buffer_allocs_cache.has(p_id));
|
||||
glDeleteRenderbuffers(1, &p_id);
|
||||
render_buffer_mem_cache -= render_buffer_allocs_cache[p_id].size;
|
||||
render_buffer_allocs_cache.erase(p_id);
|
||||
}
|
||||
|
||||
// Records that data was allocated for state tracking purposes.
|
||||
_FORCE_INLINE_ void texture_allocated_data(GLuint p_id, uint32_t p_size, String p_name = "") {
|
||||
texture_mem_cache += p_size;
|
||||
|
|
|
@ -129,9 +129,11 @@
|
|||
</member>
|
||||
<member name="foveation_dynamic" type="bool" setter="set_foveation_dynamic" getter="get_foveation_dynamic" default="false">
|
||||
Enable dynamic foveation adjustment, the interface must be initialized before this is accessible. If enabled foveation will automatically adjusted between low and [member foveation_level].
|
||||
[b]Note:[/b] Only works on compatibility renderer.
|
||||
</member>
|
||||
<member name="foveation_level" type="int" setter="set_foveation_level" getter="get_foveation_level" default="0">
|
||||
Set foveation level from 0 (off) to 3 (high), the interface must be initialized before this is accessible.
|
||||
[b]Note:[/b] Only works on compatibility renderer.
|
||||
</member>
|
||||
<member name="render_target_size_multiplier" type="float" setter="set_render_target_size_multiplier" getter="get_render_target_size_multiplier" default="1.0">
|
||||
The render size multiplier for the current HMD. Must be set before the interface has been initialized.
|
||||
|
|
Loading…
Reference in a new issue