Implement ability to render viewports directly to screen

This commit is contained in:
clayjohn 2019-05-08 11:25:34 -07:00
parent 86a74e2cb3
commit 65c211d303
18 changed files with 178 additions and 4 deletions

View file

@ -253,6 +253,9 @@
<member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking">
If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process. Default value: [code]false[/code].
</member>
<member name="render_direct_to_screen" type="bool" setter="set_use_render_direct_to_screen" getter="is_using_render_direct_to_screen">
If [code]true[/code], renders the Viewport directly to the screen instead of to the root viewport. Only available in GLES2. This is a low-level optimization and should not be used in most cases. If used, reading from the Viewport or from [code]SCREEN_TEXTURE[/code] becomes unavailable. For more information see [method VisualServer.viewport_set_render_direct_to_screen]. Default value: [code]false[/code].
</member>
<member name="render_target_clear_mode" type="int" setter="set_clear_mode" getter="get_clear_mode" enum="Viewport.ClearMode">
The clear mode when viewport used as a render target. Default value: [code]CLEAR_MODE_ALWAYS[/code].
</member>

View file

@ -3590,7 +3590,14 @@
<argument index="2" name="screen" type="int" default="0">
</argument>
<description>
Attaches a viewport to a screen.
Copies viewport to a region of the screen specified by [code]rect[/code]. If Viewport.[member Viewport.render_direct_to_screen] is [code]true[/code], then viewport does not use a framebuffer and the contents of the viewport are rendered directly to screen. However, note that the root viewport is drawn last, therefore it will draw over the screen. Accordingly, you must set the root viewport to an area that does not cover the area that you have attached this viewport to.
For example, you can set the root viewport to not render at all with the following code:
[codeblock]
func _ready():
get_viewport().set_attach_to_screen_rect(Rect2())
$Viewport.set_attach_to_screen_rect(Rect2(0, 0, 600, 600))
[/codeblock]
Using this can result in significant optimization, especially on lower-end devices. However, it comes at the cost of having to manage your viewports manually. For a further optimization see, [method set_render_direct_to_screen].
</description>
</method>
<method name="viewport_create">
@ -3789,6 +3796,17 @@
Sets the viewport's parent to another viewport.
</description>
</method>
<method name="viewport_set_render_direct_to_screen">
<return type="void">
</return>
<argument index="0" name="viewport" type="RID">
</argument>
<argument index="1" name="enabled" type="bool">
</argument>
<description>
If [code]true[/code], render the contents of the viewport directly to screen. This allows a low-level optimization where you can skip drawing a viewport to the root viewport. While this optimization can result in a significant increase in speed (especially on older devices), it comes at a cost of usability. When this is enabled, you cannot read from the viewport or from the [code]SCREEN_TEXTURE[/code]. You also lose the benefit of certain window settings, such as the various stretch modes. Another consequence to be aware of is that in 2D the rendering happens in window coordinates, so if you have a viewport that is double the size of the window, and you set this, then only the portion that fits within the window will be drawn, no automatic scaling is possible, even if your game scene is significantly larger than the window size.
</description>
</method>
<method name="viewport_set_scenario">
<return type="void">
</return>

View file

@ -692,6 +692,7 @@ public:
/* RENDER TARGET */
RID render_target_create() { return RID(); }
void render_target_set_position(RID p_render_target, int p_x, int p_y) {}
void render_target_set_size(RID p_render_target, int p_width, int p_height) {}
RID render_target_get_texture(RID p_render_target) const { return RID(); }
void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) {}

View file

@ -113,9 +113,22 @@ void RasterizerCanvasGLES2::canvas_begin() {
state.canvas_shader.bind();
state.using_transparent_rt = false;
int viewport_x, viewport_y, viewport_width, viewport_height;
if (storage->frame.current_rt) {
glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->fbo);
state.using_transparent_rt = storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_TRANSPARENT];
if (storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN]) {
// set Viewport and Scissor when rendering directly to screen
viewport_width = storage->frame.current_rt->width;
viewport_height = storage->frame.current_rt->height;
viewport_x = storage->frame.current_rt->x;
viewport_y = OS::get_singleton()->get_window_size().height - viewport_height - storage->frame.current_rt->y;
glScissor(viewport_x, viewport_y, viewport_width, viewport_height);
glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
glEnable(GL_SCISSOR_TEST);
}
}
if (storage->frame.clear_request) {
@ -179,6 +192,14 @@ void RasterizerCanvasGLES2::canvas_end() {
glDisableVertexAttribArray(i);
}
if (storage->frame.current_rt && storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN]) {
//reset viewport to full window size
int viewport_width = OS::get_singleton()->get_window_size().width;
int viewport_height = OS::get_singleton()->get_window_size().height;
glViewport(0, 0, viewport_width, viewport_height);
glScissor(0, 0, viewport_width, viewport_height);
}
state.using_texture_rect = false;
state.using_skeleton = false;
state.using_ninepatch = false;
@ -1192,6 +1213,11 @@ void RasterizerCanvasGLES2::_canvas_item_render_commands(Item *p_item, Item *cur
void RasterizerCanvasGLES2::_copy_screen(const Rect2 &p_rect) {
if (storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN]) {
ERR_PRINT_ONCE("Cannot use screen texture copying in render target set to render direct to screen");
return;
}
if (storage->frame.current_rt->copy_screen_effect.color == 0) {
ERR_EXPLAIN("Can't use screen texture copying in a render target configured without copy buffers");
ERR_FAIL();

View file

@ -2694,6 +2694,7 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const
Environment *env = NULL;
int viewport_width, viewport_height;
int viewport_x, viewport_y;
bool probe_interior = false;
bool reverse_cull = false;
@ -2733,6 +2734,13 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const
viewport_width = storage->frame.current_rt->width;
viewport_height = storage->frame.current_rt->height;
viewport_x = storage->frame.current_rt->x;
if (storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN]) {
viewport_y = OS::get_singleton()->get_window_size().height - viewport_height - storage->frame.current_rt->y;
} else {
viewport_y = storage->frame.current_rt->y;
}
}
state.used_screen_texture = false;
@ -2798,7 +2806,13 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const
// other stuff
glBindFramebuffer(GL_FRAMEBUFFER, current_fb);
glViewport(0, 0, viewport_width, viewport_height);
glViewport(viewport_x, viewport_y, viewport_width, viewport_height);
if (storage->frame.current_rt && storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN]) {
glScissor(viewport_x, viewport_y, viewport_width, viewport_height);
glEnable(GL_SCISSOR_TEST);
}
glDepthFunc(GL_LEQUAL);
glDepthMask(GL_TRUE);
@ -2832,6 +2846,10 @@ void RasterizerSceneGLES2::render_scene(const Transform &p_cam_transform, const
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
if (storage->frame.current_rt && storage->frame.current_rt->flags[RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN]) {
glDisable(GL_SCISSOR_TEST);
}
glVertexAttrib4f(VS::ARRAY_COLOR, 1, 1, 1, 1);
glBlendEquation(GL_FUNC_ADD);

View file

@ -4568,9 +4568,16 @@ void RasterizerStorageGLES2::instance_remove_dependency(RID p_base, RasterizerSc
void RasterizerStorageGLES2::_render_target_allocate(RenderTarget *rt) {
// do not allocate a render target with no size
if (rt->width <= 0 || rt->height <= 0)
return;
// do not allocate a render target that is attached to the screen
if (rt->flags[RENDER_TARGET_DIRECT_TO_SCREEN]) {
rt->fbo = RasterizerStorageGLES2::system_fbo;
return;
}
GLuint color_internal_format;
GLuint color_format;
GLuint color_type = GL_UNSIGNED_BYTE;
@ -4779,6 +4786,10 @@ void RasterizerStorageGLES2::_render_target_allocate(RenderTarget *rt) {
void RasterizerStorageGLES2::_render_target_clear(RenderTarget *rt) {
// there is nothing to clear when DIRECT_TO_SCREEN is used
if (rt->flags[RENDER_TARGET_DIRECT_TO_SCREEN])
return;
if (rt->fbo) {
glDeleteFramebuffers(1, &rt->fbo);
glDeleteTextures(1, &rt->color);
@ -4873,6 +4884,15 @@ RID RasterizerStorageGLES2::render_target_create() {
return render_target_owner.make_rid(rt);
}
void RasterizerStorageGLES2::render_target_set_position(RID p_render_target, int p_x, int p_y) {
RenderTarget *rt = render_target_owner.getornull(p_render_target);
ERR_FAIL_COND(!rt);
rt->x = p_x;
rt->y = p_y;
}
void RasterizerStorageGLES2::render_target_set_size(RID p_render_target, int p_width, int p_height) {
RenderTarget *rt = render_target_owner.getornull(p_render_target);
@ -5002,6 +5022,14 @@ void RasterizerStorageGLES2::render_target_set_flag(RID p_render_target, RenderT
RenderTarget *rt = render_target_owner.getornull(p_render_target);
ERR_FAIL_COND(!rt);
// When setting DIRECT_TO_SCREEN, you need to clear before the value is set, but allocate after as
// those functions change how they operate depending on the value of DIRECT_TO_SCREEN
if (p_flag == RENDER_TARGET_DIRECT_TO_SCREEN && p_value != rt->flags[RENDER_TARGET_DIRECT_TO_SCREEN]) {
_render_target_clear(rt);
rt->flags[p_flag] = p_value;
_render_target_allocate(rt);
}
rt->flags[p_flag] = p_value;
switch (p_flag) {

View file

@ -1175,7 +1175,7 @@ public:
}
} external;
int width, height;
int x, y, width, height;
bool flags[RENDER_TARGET_FLAG_MAX];
@ -1192,6 +1192,8 @@ public:
multisample_color(0),
multisample_depth(0),
multisample_active(false),
x(0),
y(0),
width(0),
height(0),
used_in_frame(false),
@ -1209,6 +1211,7 @@ public:
void _render_target_allocate(RenderTarget *rt);
virtual RID render_target_create();
virtual void render_target_set_position(RID p_render_target, int p_x, int p_y);
virtual void render_target_set_size(RID p_render_target, int p_width, int p_height);
virtual RID render_target_get_texture(RID p_render_target) const;
virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id);

View file

@ -7268,6 +7268,10 @@ RID RasterizerStorageGLES3::render_target_create() {
return render_target_owner.make_rid(rt);
}
void RasterizerStorageGLES3::render_target_set_position(RID p_render_target, int p_x, int p_y) {
//only used in GLES2
}
void RasterizerStorageGLES3::render_target_set_size(RID p_render_target, int p_width, int p_height) {
RenderTarget *rt = render_target_owner.getornull(p_render_target);

View file

@ -1394,6 +1394,7 @@ public:
void _render_target_allocate(RenderTarget *rt);
virtual RID render_target_create();
virtual void render_target_set_position(RID p_render_target, int p_x, int p_y);
virtual void render_target_set_size(RID p_render_target, int p_width, int p_height);
virtual RID render_target_get_texture(RID p_render_target) const;
virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id);

View file

@ -2765,6 +2765,19 @@ Rect2 Viewport::get_attach_to_screen_rect() const {
return to_screen_rect;
}
void Viewport::set_use_render_direct_to_screen(bool p_render_direct_to_screen) {
if (p_render_direct_to_screen == render_direct_to_screen)
return;
render_direct_to_screen = p_render_direct_to_screen;
VS::get_singleton()->viewport_set_render_direct_to_screen(viewport, p_render_direct_to_screen);
}
bool Viewport::is_using_render_direct_to_screen() const {
return render_direct_to_screen;
}
void Viewport::set_physics_object_picking(bool p_enable) {
physics_object_picking = p_enable;
@ -3030,6 +3043,8 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d);
ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d);
ClassDB::bind_method(D_METHOD("set_attach_to_screen_rect", "rect"), &Viewport::set_attach_to_screen_rect);
ClassDB::bind_method(D_METHOD("set_use_render_direct_to_screen", "enable"), &Viewport::set_use_render_direct_to_screen);
ClassDB::bind_method(D_METHOD("is_using_render_direct_to_screen"), &Viewport::is_using_render_direct_to_screen);
ClassDB::bind_method(D_METHOD("get_mouse_position"), &Viewport::get_mouse_position);
ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Viewport::warp_mouse);
@ -3084,6 +3099,7 @@ void Viewport::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_3d_linear"), "set_keep_3d_linear", "get_keep_3d_linear");
ADD_PROPERTY(PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_ENUM, "2D,2D No-Sampling,3D,3D No-Effects"), "set_usage", "get_usage");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "render_direct_to_screen"), "set_use_render_direct_to_screen", "is_using_render_direct_to_screen");
ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_draw", PROPERTY_HINT_ENUM, "Disabled,Unshaded,Overdraw,Wireframe"), "set_debug_draw", "get_debug_draw");
ADD_GROUP("Render Target", "render_target_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "render_target_v_flip"), "set_vflip", "get_vflip");
@ -3166,6 +3182,8 @@ Viewport::Viewport() {
texture_rid = VisualServer::get_singleton()->viewport_get_texture(viewport);
texture_flags = 0;
render_direct_to_screen = false;
default_texture.instance();
default_texture->vp = const_cast<Viewport *>(this);
viewport_textures.insert(default_texture.ptr());

View file

@ -182,6 +182,7 @@ private:
Size2 size;
Rect2 to_screen_rect;
bool render_direct_to_screen;
RID contact_2d_debug;
RID contact_3d_debug_multimesh;
@ -481,6 +482,9 @@ public:
void set_attach_to_screen_rect(const Rect2 &p_rect);
Rect2 get_attach_to_screen_rect() const;
void set_use_render_direct_to_screen(bool p_render_direct_to_screen);
bool is_using_render_direct_to_screen() const;
Vector2 get_mouse_position() const;
void warp_mouse(const Vector2 &p_pos);

View file

@ -550,10 +550,12 @@ public:
RENDER_TARGET_NO_SAMPLING,
RENDER_TARGET_HDR,
RENDER_TARGET_KEEP_3D_LINEAR,
RENDER_TARGET_DIRECT_TO_SCREEN,
RENDER_TARGET_FLAG_MAX
};
virtual RID render_target_create() = 0;
virtual void render_target_set_position(RID p_render_target, int p_x, int p_y) = 0;
virtual void render_target_set_size(RID p_render_target, int p_width, int p_height) = 0;
virtual RID render_target_get_texture(RID p_render_target) const = 0;
virtual void render_target_set_external_texture(RID p_render_target, unsigned int p_texture_id) = 0;

View file

@ -454,6 +454,7 @@ public:
BIND2(viewport_set_clear_mode, RID, ViewportClearMode)
BIND3(viewport_attach_to_screen, RID, const Rect2 &, int)
BIND2(viewport_set_render_direct_to_screen, RID, bool)
BIND1(viewport_detach, RID)
BIND2(viewport_set_update_mode, RID, ViewportUpdateMode)

View file

@ -343,7 +343,7 @@ void VisualServerViewport::draw_viewports() {
vp->render_info[VS::VIEWPORT_RENDER_INFO_SURFACE_CHANGES_IN_FRAME] = VSG::storage->get_captured_render_info(VS::INFO_SURFACE_CHANGES_IN_FRAME);
vp->render_info[VS::VIEWPORT_RENDER_INFO_DRAW_CALLS_IN_FRAME] = VSG::storage->get_captured_render_info(VS::INFO_DRAW_CALLS_IN_FRAME);
if (vp->viewport_to_screen_rect != Rect2()) {
if (vp->viewport_to_screen_rect != Rect2() && (!vp->viewport_render_direct_to_screen || !VSG::rasterizer->is_low_end())) {
//copy to screen if set as such
VSG::rasterizer->set_current_render_target(RID());
VSG::rasterizer->blit_render_target_to_screen(vp->render_target, vp->viewport_to_screen_rect, vp->viewport_to_screen);
@ -368,6 +368,7 @@ RID VisualServerViewport::viewport_create() {
viewport->hide_canvas = false;
viewport->render_target = VSG::storage->render_target_create();
viewport->shadow_atlas = VSG::scene_render->shadow_atlas_create();
viewport->viewport_render_direct_to_screen = false;
return rid;
}
@ -424,14 +425,55 @@ void VisualServerViewport::viewport_attach_to_screen(RID p_viewport, const Rect2
Viewport *viewport = viewport_owner.getornull(p_viewport);
ERR_FAIL_COND(!viewport);
// If using GLES2 we can optimize this operation by rendering directly to system_fbo
// instead of rendering to fbo and copying to system_fbo after
if (VSG::rasterizer->is_low_end() && viewport->viewport_render_direct_to_screen) {
VSG::storage->render_target_set_size(viewport->render_target, p_rect.size.x, p_rect.size.y);
VSG::storage->render_target_set_position(viewport->render_target, p_rect.position.x, p_rect.position.y);
}
viewport->viewport_to_screen_rect = p_rect;
viewport->viewport_to_screen = p_screen;
}
void VisualServerViewport::viewport_set_render_direct_to_screen(RID p_viewport, bool p_enable) {
Viewport *viewport = viewport_owner.getornull(p_viewport);
ERR_FAIL_COND(!viewport);
if (p_enable == viewport->viewport_render_direct_to_screen)
return;
// if disabled, reset render_target size and position
if (!p_enable) {
VSG::storage->render_target_set_position(viewport->render_target, 0, 0);
VSG::storage->render_target_set_size(viewport->render_target, viewport->size.x, viewport->size.y);
}
VSG::storage->render_target_set_flag(viewport->render_target, RasterizerStorage::RENDER_TARGET_DIRECT_TO_SCREEN, p_enable);
viewport->viewport_render_direct_to_screen = p_enable;
// if attached to screen already, setup screen size and position, this needs to happen after setting flag to avoid an unneccesary buffer allocation
if (VSG::rasterizer->is_low_end() && viewport->viewport_to_screen_rect != Rect2() && p_enable) {
VSG::storage->render_target_set_size(viewport->render_target, viewport->viewport_to_screen_rect.size.x, viewport->viewport_to_screen_rect.size.y);
VSG::storage->render_target_set_position(viewport->render_target, viewport->viewport_to_screen_rect.position.x, viewport->viewport_to_screen_rect.position.y);
}
}
void VisualServerViewport::viewport_detach(RID p_viewport) {
Viewport *viewport = viewport_owner.getornull(p_viewport);
ERR_FAIL_COND(!viewport);
// if render_direct_to_screen was used, reset size and position
if (VSG::rasterizer->is_low_end() && viewport->viewport_render_direct_to_screen) {
VSG::storage->render_target_set_position(viewport->render_target, 0, 0);
VSG::storage->render_target_set_size(viewport->render_target, viewport->size.x, viewport->size.y);
}
viewport->viewport_to_screen_rect = Rect2();
viewport->viewport_to_screen = 0;
}

View file

@ -58,6 +58,7 @@ public:
int viewport_to_screen;
Rect2 viewport_to_screen_rect;
bool viewport_render_direct_to_screen;
bool hide_scenario;
bool hide_canvas;
@ -158,6 +159,7 @@ public:
void viewport_set_size(RID p_viewport, int p_width, int p_height);
void viewport_attach_to_screen(RID p_viewport, const Rect2 &p_rect = Rect2(), int p_screen = 0);
void viewport_set_render_direct_to_screen(RID p_viewport, bool p_enable);
void viewport_detach(RID p_viewport);
void viewport_set_active(RID p_viewport, bool p_active);

View file

@ -381,6 +381,7 @@ public:
FUNC2(viewport_set_clear_mode, RID, ViewportClearMode)
FUNC3(viewport_attach_to_screen, RID, const Rect2 &, int)
FUNC2(viewport_set_render_direct_to_screen, RID, bool)
FUNC1(viewport_detach, RID)
FUNC2(viewport_set_update_mode, RID, ViewportUpdateMode)

View file

@ -1876,6 +1876,7 @@ void VisualServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("viewport_set_active", "viewport", "active"), &VisualServer::viewport_set_active);
ClassDB::bind_method(D_METHOD("viewport_set_parent_viewport", "viewport", "parent_viewport"), &VisualServer::viewport_set_parent_viewport);
ClassDB::bind_method(D_METHOD("viewport_attach_to_screen", "viewport", "rect", "screen"), &VisualServer::viewport_attach_to_screen, DEFVAL(Rect2()), DEFVAL(0));
ClassDB::bind_method(D_METHOD("viewport_set_render_direct_to_screen", "viewport", "enabled"), &VisualServer::viewport_set_render_direct_to_screen);
ClassDB::bind_method(D_METHOD("viewport_detach", "viewport"), &VisualServer::viewport_detach);
ClassDB::bind_method(D_METHOD("viewport_set_update_mode", "viewport", "update_mode"), &VisualServer::viewport_set_update_mode);
ClassDB::bind_method(D_METHOD("viewport_set_vflip", "viewport", "enabled"), &VisualServer::viewport_set_vflip);

View file

@ -610,6 +610,7 @@ public:
virtual void viewport_set_parent_viewport(RID p_viewport, RID p_parent_viewport) = 0;
virtual void viewport_attach_to_screen(RID p_viewport, const Rect2 &p_rect = Rect2(), int p_screen = 0) = 0;
virtual void viewport_set_render_direct_to_screen(RID p_viewport, bool p_enable) = 0;
virtual void viewport_detach(RID p_viewport) = 0;
enum ViewportUpdateMode {