From 42ec01253bf81689268027cb483a4c23e823279d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 30 Mar 2017 22:15:23 -0400 Subject: [PATCH] gamnit: intro dynamic resolution support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Alexis Laferrière --- lib/gamnit/depth/depth.nit | 4 + lib/gamnit/dynamic_resolution.nit | 316 +++++++++++++++++++++++++++++++++++++ lib/gamnit/flat.nit | 6 +- 3 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 lib/gamnit/dynamic_resolution.nit diff --git a/lib/gamnit/depth/depth.nit b/lib/gamnit/depth/depth.nit index a9a446a..f18f2eb 100644 --- a/lib/gamnit/depth/depth.nit +++ b/lib/gamnit/depth/depth.nit @@ -45,6 +45,8 @@ redef class App # Draw all elements of `actors` and then call `frame_core_flat` protected fun frame_core_depth(display: GamnitDisplay) do + frame_core_dynamic_resolution_before display + # Update cameras on both our programs versatile_program.use versatile_program.mvp.uniform world_camera.mvp_matrix @@ -71,6 +73,8 @@ redef class App frame_core_ui_sprites display perfs["gamnit depth ui_sprites"].add frame_core_depth_clock.lapse + + frame_core_dynamic_resolution_after display end private var frame_core_depth_clock = new Clock diff --git a/lib/gamnit/dynamic_resolution.nit b/lib/gamnit/dynamic_resolution.nit new file mode 100644 index 0000000..40ea221 --- /dev/null +++ b/lib/gamnit/dynamic_resolution.nit @@ -0,0 +1,316 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Virtual screen with a resolution independent from the real screen +# +# The attribute `dynamic_resolution_ratio` sets the size of the virtual screen +# in relation to the real screen. See its documentation for more information. +# +# Reference implementation: +# https://software.intel.com/en-us/articles/dynamic-resolution-rendering-on-opengl-es-2 +module dynamic_resolution + +import performance_analysis + +import gamnit + +redef class App + # Resolution of the dynamic screen as ratio of the real screen resolution. + # + # - At 1.0, the default, the virtual screen is not used and the visuals are + # drawn directly to the real screen and pixels. + # - When below 1.0, there is less pixels in the dynamic screen than in the + # real screen. This reduces the strain on the GPU, especially of high + # resolution displays. + # - Values above 1.0 are not supported at this point, but they would allow + # super-sampling. + # + # This value must be set either by the user using a video quality slider or + # by an heuristic according to the device capabilities. + # A lower value should use less battery power on mobile devices. + # + # This value is applied to both X and Y, so it has an exponential effect on + # the number of pixels. + var dynamic_resolution_ratio = 1.0 + + # Minimum dynamic screen resolution + var min_dynamic_resolution_ratio = 0.0125 is writable + + # Maximum dynamic screen resolution, must be set before first draw + var max_dynamic_resolution_ratio = 1.0 is writable + + private var dynres_program = new DynamicResolutionProgram + + private var perf_clock_dynamic_resolution = new Clock is lazy + + redef fun on_create + do + super + + var program = dynres_program + program.compile_and_link + var error = program.error + assert error == null else print_error error + end + + # Prepare to draw to the dynamic screen if `dynamic_resolution_ratio != 1.0` + protected fun frame_core_dynamic_resolution_before(display: GamnitDisplay) + do + # TODO autodetect when to lower/increase resolution + + if dynamic_resolution_ratio == 1.0 then + # Draw directly to the screen framebuffer + glBindFramebuffer(gl_FRAMEBUFFER, dynamic_context.screen_framebuffer) + glViewport(0, 0, display.width, display.height) + + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + return + end + + # Draw to our dynamic framebuffer + glBindFramebuffer(gl_FRAMEBUFFER, dynamic_context.dynamic_framebuffer) + glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT + + var ratio = dynamic_resolution_ratio + ratio = ratio.clamp(min_dynamic_resolution_ratio, max_dynamic_resolution_ratio) + glViewport(0, 0, (display.width.to_f*ratio).to_i, + (display.height.to_f*ratio).to_i) + + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + end + + # Draw the dynamic screen to the real screen if `dynamic_resolution_ratio != 1.0` + protected fun frame_core_dynamic_resolution_after(display: GamnitDisplay) + do + if dynamic_resolution_ratio == 1.0 then return + perf_clock_dynamic_resolution.lapse + + var ratio = dynamic_resolution_ratio + ratio = ratio.clamp(min_dynamic_resolution_ratio, max_dynamic_resolution_ratio) + + glBindFramebuffer(gl_FRAMEBUFFER, dynamic_context.screen_framebuffer) + glBindBuffer(gl_ARRAY_BUFFER, dynamic_context.buffer_array) + glViewport(0, 0, display.width, display.height) + glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT + dynres_program.use + + # Uniforms + glActiveTexture gl_TEXTURE0 + glBindTexture(gl_TEXTURE_2D, dynamic_context.texture) + dynres_program.texture.uniform 0 + dynres_program.ratio.uniform ratio + + # Attributes + var sizeof_gl_float = 4 + var n_floats = 3 + glEnableVertexAttribArray dynres_program.coord.location + glVertexAttribPointeri(dynres_program.coord.location, n_floats, gl_FLOAT, false, 0, 0) + var offset = 4 * n_floats * sizeof_gl_float + + n_floats = 2 + glEnableVertexAttribArray dynres_program.tex_coord.location + glVertexAttribPointeri(dynres_program.tex_coord.location, n_floats, gl_FLOAT, false, 0, offset) + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + # Draw + glDrawArrays(gl_TRIANGLE_STRIP, 0, 4) + + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + sys.perfs["gamnit flat dyn res"].add app.perf_clock_dynamic_resolution.lapse + end + + # Framebuffer and texture for dynamic resolution intermediate drawing + private var dynamic_context: DynamicContext = create_dynamic_context is lazy + + private fun create_dynamic_context: DynamicContext + do + # TODO option or flag to regen on real resolution change. + + var display = display + assert display != null + + var context = new DynamicContext + context.prepare_once(display, max_dynamic_resolution_ratio) + return context + end +end + +# Program drawing the dynamic screen to the real screen +private class DynamicContext + + # Real screen framebuffer + var screen_framebuffer: Int = -1 + + # Dynamic screen framebuffer + var dynamic_framebuffer: Int = -1 + + # Depth renderbuffer attached to `dynamic_framebuffer` + var depth_renderbuffer: Int = -1 + + # Texture attached to `dynamic_framebuffer` as color attachment + var texture: Int = -1 + + # Buffer name for vertex data + var buffer_array: Int = -1 + + var float_per_vertex: Int is lazy do return 4 + 4 + 3 + + # Prepare all attributes once per resolution change + fun prepare_once(display: GamnitDisplay, max_dynamic_resolution_ratio: Float) + do + # TODO enable antialiasing. + + var width = (display.width.to_f * max_dynamic_resolution_ratio).to_i + var height = (display.height.to_f * max_dynamic_resolution_ratio).to_i + + # Set aside the real screen framebuffer name + var screen_framebuffer = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0) + self.screen_framebuffer = screen_framebuffer + + # Framebuffer + var framebuffer = glGenFramebuffers(1).first + glBindFramebuffer(gl_FRAMEBUFFER, framebuffer) + assert glIsFramebuffer(framebuffer) + self.dynamic_framebuffer = framebuffer + + # Depth + var depthbuffer = glGenRenderbuffers(1).first + glBindRenderbuffer(gl_RENDERBUFFER, depthbuffer) + assert glIsRenderbuffer(depthbuffer) + glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPNENT16, width, height) + glFramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_RENDERBUFFER, depthbuffer) + self.depth_renderbuffer = depthbuffer + + # Texture + var texture = glGenTextures(1).first + glBindTexture(gl_TEXTURE_2D, texture) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE) + glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE) + glTexImage2D(gl_TEXTURE_2D, 0, gl_RGB, width, height, + 0, gl_RGB, gl_UNSIGNED_BYTE, new Pointer.nul) + glFramebufferTexture2D(gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, gl_TEXTURE_2D, texture, 0) + self.texture = texture + + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE + + # Take down + glBindRenderbuffer(gl_RENDERBUFFER, 0) + glBindFramebuffer(gl_FRAMEBUFFER, 0) + + # Array buffer + buffer_array = glGenBuffers(1).first + glBindBuffer(gl_ARRAY_BUFFER, buffer_array) + assert glIsBuffer(buffer_array) + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + + ## coord + var data = new Array[Float] + data.add_all([-1.0, -1.0, 0.0, + 1.0, -1.0, 0.0, + -1.0, 1.0, 0.0, + 1.0, 1.0, 0.0]) + ## tex_coord + data.add_all([0.0, 0.0, + 1.0, 0.0, + 0.0, 1.0, + 1.0, 1.0]) + var c_data = new GLfloatArray.from(data) + glBufferData(gl_ARRAY_BUFFER, data.length*4, c_data.native_array, gl_STATIC_DRAW) + + glBindBuffer(gl_ARRAY_BUFFER, 0) + + gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + end + + var destroyed = false + fun destroy + do + if destroyed then return + destroyed = true + + # Free the buffer + glDeleteBuffers([buffer_array]) + var gl_error = glGetError + assert gl_error == gl_NO_ERROR else print_error gl_error + buffer_array = -1 + + # Free the dynamic framebuffer and its attachments + glDeleteBuffers([buffer_array]) + glDeleteFramebuffers([dynamic_framebuffer]) + glDeleteRenderbuffers([depth_renderbuffer]) + glDeleteTextures([texture]) + end +end + +private class DynamicResolutionProgram + super GamnitProgramFromSource + + redef var vertex_shader_source = """ + // Vertex coordinates + attribute vec3 coord; + + // Vertex coordinates on textures + attribute vec2 tex_coord; + + // Output to the fragment shader + varying vec2 v_coord; + + void main() + { + gl_Position = vec4(coord, 1.0); + v_coord = tex_coord; + } + """ @ glsl_vertex_shader + + redef var fragment_shader_source = """ + precision mediump float; + + // Virtual screen texture / color attachment + uniform sampler2D texture0; + + // Input from the vertex shader + varying vec2 v_coord; + + // Ratio of the virtual screen to draw + uniform float ratio; + + void main() + { + gl_FragColor = texture2D(texture0, v_coord*ratio); + } + """ @ glsl_fragment_shader + + # Vertices coordinates + var coord = attributes["coord"].as(AttributeVec3) is lazy + + # Coordinates on the textures, per vertex + var tex_coord = attributes["tex_coord"].as(AttributeVec2) is lazy + + # Virtual screen texture / color attachment + var texture = uniforms["texture0"].as(UniformSampler2D) is lazy + + # Ratio of the virtual screen to draw + var ratio = uniforms["ratio"].as(UniformFloat) is lazy +end diff --git a/lib/gamnit/flat.nit b/lib/gamnit/flat.nit index d5cb28e..f6e0fd2 100644 --- a/lib/gamnit/flat.nit +++ b/lib/gamnit/flat.nit @@ -41,6 +41,7 @@ import performance_analysis import gamnit import gamnit::cameras +import gamnit::dynamic_resolution import gamnit::limit_fps import android_two_fingers_motion is conditional(android) @@ -355,13 +356,16 @@ redef class App # Draw the whole screen, all `glDraw...` calls should be executed here protected fun frame_core_draw(display: GamnitDisplay) do - perf_clock_main.lapse + frame_core_dynamic_resolution_before display + perf_clock_main.lapse frame_core_world_sprites display perfs["gamnit flat world_sprites"].add perf_clock_main.lapse frame_core_ui_sprites display perfs["gamnit flat ui_sprites"].add perf_clock_main.lapse + + frame_core_dynamic_resolution_after display end private fun frame_core_sprites(display: GamnitDisplay, sprite_set: SpriteSet, camera: Camera) -- 1.7.9.5