1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Virtual screen with a resolution independent from the real screen
17 # The attribute `dynamic_resolution_ratio` sets the size of the virtual screen
18 # in relation to the real screen. See its documentation for more information.
20 # Reference implementation:
21 # https://software.intel.com/en-us/articles/dynamic-resolution-rendering-on-opengl-es-2
22 module dynamic_resolution
24 import performance_analysis
29 # Resolution of the dynamic screen as ratio of the real screen resolution.
31 # - At 1.0, the default, the virtual screen is not used and the visuals are
32 # drawn directly to the real screen and pixels.
33 # - When below 1.0, there is less pixels in the dynamic screen than in the
34 # real screen. This reduces the strain on the GPU, especially of high
35 # resolution displays.
36 # - Values above 1.0 are not supported at this point, but they would allow
39 # This value must be set either by the user using a video quality slider or
40 # by an heuristic according to the device capabilities.
41 # A lower value should use less battery power on mobile devices.
43 # This value is applied to both X and Y, so it has an exponential effect on
44 # the number of pixels.
45 var dynamic_resolution_ratio
= 1.0 is writable
47 # Minimum dynamic screen resolution
48 var min_dynamic_resolution_ratio
= 0.0125 is writable
50 # Maximum dynamic screen resolution, must be set before first draw
51 var max_dynamic_resolution_ratio
= 1.0 is writable
53 private var dynres_program
= new DynamicResolutionProgram
55 private var perf_clock_dynamic_resolution
= new Clock is lazy
57 # Real screen framebuffer
58 private var screen_framebuffer_cache
: Int = -1
60 # Real screen framebuffer name
61 fun screen_framebuffer
: Int
63 var cache
= screen_framebuffer_cache
64 if cache
!= -1 then return cache
66 cache
= glGetIntegerv
(gl_FRAMEBUFFER_BINDING
, 0)
67 self.screen_framebuffer_cache
= cache
71 redef fun create_gamnit
75 var program
= dynres_program
76 program
.compile_and_link
77 var error
= program
.error
78 assert error
== null else print_error error
80 dynamic_context_cache
= null
83 redef fun on_resize
(display
)
85 if dynamic_context_cache
!= null then dynamic_context
.resize
(display
, max_dynamic_resolution_ratio
)
89 # Prepare to draw to the dynamic screen if `dynamic_resolution_ratio != 1.0`
90 protected fun frame_core_dynamic_resolution_before
(display
: GamnitDisplay)
92 # TODO autodetect when to lower/increase resolution
94 if dynamic_resolution_ratio
== 1.0 then
95 # Draw directly to the screen framebuffer
96 glBindFramebuffer
(gl_FRAMEBUFFER
, screen_framebuffer
)
97 glViewport
(0, 0, display
.width
, display
.height
)
98 glClear gl_COLOR_BUFFER_BIT
| gl_DEPTH_BUFFER_BIT
100 var gl_error
= glGetError
101 assert gl_error
== gl_NO_ERROR
else print_error gl_error
105 # Draw to our dynamic framebuffer
106 glBindFramebuffer
(gl_FRAMEBUFFER
, dynamic_context
.dynamic_framebuffer
)
108 var ratio
= dynamic_resolution_ratio
109 ratio
= ratio
.clamp
(min_dynamic_resolution_ratio
, max_dynamic_resolution_ratio
)
110 glViewport
(0, 0, (display
.width
.to_f
*ratio
).to_i
,
111 (display
.height
.to_f
*ratio
).to_i
)
113 glClear gl_COLOR_BUFFER_BIT
| gl_DEPTH_BUFFER_BIT
115 var gl_error
= glGetError
116 assert gl_error
== gl_NO_ERROR
else print_error gl_error
119 # Draw the dynamic screen to the real screen if `dynamic_resolution_ratio != 1.0`
120 protected fun frame_core_dynamic_resolution_after
(display
: GamnitDisplay)
122 if dynamic_resolution_ratio
== 1.0 then return
123 perf_clock_dynamic_resolution
.lapse
125 var ratio
= dynamic_resolution_ratio
126 ratio
= ratio
.clamp
(min_dynamic_resolution_ratio
, max_dynamic_resolution_ratio
)
128 glBindFramebuffer
(gl_FRAMEBUFFER
, screen_framebuffer
)
129 glBindBuffer
(gl_ARRAY_BUFFER
, dynamic_context
.buffer_array
)
130 glViewport
(0, 0, display
.width
, display
.height
)
131 glClear gl_COLOR_BUFFER_BIT
| gl_DEPTH_BUFFER_BIT
135 glActiveTexture gl_TEXTURE0
136 glBindTexture
(gl_TEXTURE_2D
, dynamic_context
.texture
)
137 dynres_program
.texture
.uniform
0
138 dynres_program
.ratio
.uniform ratio
141 var sizeof_gl_float
= 4
143 glEnableVertexAttribArray dynres_program
.coord
.location
144 glVertexAttribPointeri
(dynres_program
.coord
.location
, n_floats
, gl_FLOAT
, false, 0, 0)
145 var offset
= 4 * n_floats
* sizeof_gl_float
148 glEnableVertexAttribArray dynres_program
.tex_coord
.location
149 glVertexAttribPointeri
(dynres_program
.tex_coord
.location
, n_floats
, gl_FLOAT
, false, 0, offset
)
150 var gl_error
= glGetError
151 assert gl_error
== gl_NO_ERROR
else print_error gl_error
154 glDrawArrays
(gl_TRIANGLE_STRIP
, 0, 4)
155 gl_error
= glGetError
156 assert gl_error
== gl_NO_ERROR
else print_error gl_error
159 glBindBuffer
(gl_ARRAY_BUFFER
, 0)
160 gl_error
= glGetError
161 assert gl_error
== gl_NO_ERROR
else print_error gl_error
163 sys
.perfs
["gamnit flat dyn res"].add app
.perf_clock_dynamic_resolution
.lapse
166 # Framebuffer and texture for dynamic resolution intermediate drawing
167 private fun dynamic_context
: DynamicContext
169 var cache
= dynamic_context_cache
170 if cache
!= null then return cache
172 cache
= create_dynamic_context
173 dynamic_context_cache
= cache
177 private var dynamic_context_cache
: nullable DynamicContext = null
179 private fun create_dynamic_context
: DynamicContext
181 # TODO option or flag to regen on real resolution change.
183 var display
= display
184 assert display
!= null
186 var context
= new DynamicContext
187 context
.prepare_once
(display
, max_dynamic_resolution_ratio
)
192 # Handles to reused GL buffers and texture
193 private class DynamicContext
195 # Dynamic screen framebuffer
196 var dynamic_framebuffer
: Int = -1
198 # Depth renderbuffer attached to `dynamic_framebuffer`
199 var depth_renderbuffer
: Int = -1
201 # Texture attached to `dynamic_framebuffer` as color attachment
202 var texture
: Int = -1
204 # Buffer name for vertex data
205 var buffer_array
: Int = -1
207 # Prepare all attributes once per resolution change
208 fun prepare_once
(display
: GamnitDisplay, max_dynamic_resolution_ratio
: Float)
210 # TODO enable antialiasing.
213 var framebuffer
= glGenFramebuffers
(1).first
214 glBindFramebuffer
(gl_FRAMEBUFFER
, framebuffer
)
215 assert glIsFramebuffer
(framebuffer
)
216 self.dynamic_framebuffer
= framebuffer
217 var gl_error
= glGetError
218 assert gl_error
== gl_NO_ERROR
else print_error gl_error
220 # Depth & texture/color
221 var depthbuffer
= glGenRenderbuffers
(1).first
222 self.depth_renderbuffer
= depthbuffer
223 var texture
= glGenTextures
(1).first
224 self.texture
= texture
225 gl_error
= glGetError
226 assert gl_error
== gl_NO_ERROR
else print_error gl_error
228 resize
(display
, max_dynamic_resolution_ratio
)
229 assert glCheckFramebufferStatus
(gl_FRAMEBUFFER
) == gl_FRAMEBUFFER_COMPLETE
232 buffer_array
= glGenBuffers
(1).first
233 glBindBuffer
(gl_ARRAY_BUFFER
, buffer_array
)
234 assert glIsBuffer
(buffer_array
)
235 gl_error
= glGetError
236 assert gl_error
== gl_NO_ERROR
else print_error gl_error
239 var data
= new Array[Float]
240 data
.add_all
([-1.0, -1.0, 0.0,
245 data
.add_all
([0.0, 0.0,
249 var c_data
= new GLfloatArray.from
(data
)
250 glBufferData
(gl_ARRAY_BUFFER
, data
.length
*4, c_data
.native_array
, gl_STATIC_DRAW
)
252 glBindBuffer
(gl_ARRAY_BUFFER
, 0)
254 gl_error
= glGetError
255 assert gl_error
== gl_NO_ERROR
else print_error gl_error
258 # Init size or resize `depth_renderbuffer` and `texture`
259 fun resize
(display
: GamnitDisplay, max_dynamic_resolution_ratio
: Float)
261 var width
= (display
.width
.to_f
* max_dynamic_resolution_ratio
).to_i
262 var height
= (display
.height
.to_f
* max_dynamic_resolution_ratio
).to_i
264 glBindFramebuffer
(gl_FRAMEBUFFER
, dynamic_framebuffer
)
266 var depthbuffer
= self.depth_renderbuffer
267 var texture
= self.texture
270 glBindRenderbuffer
(gl_RENDERBUFFER
, depthbuffer
)
271 assert glIsRenderbuffer
(depthbuffer
)
272 glRenderbufferStorage
(gl_RENDERBUFFER
, gl_DEPTH_COMPONENT16
, width
, height
)
273 glFramebufferRenderbuffer
(gl_FRAMEBUFFER
, gl_DEPTH_ATTACHMENT
, gl_RENDERBUFFER
, depthbuffer
)
274 var gl_error
= glGetError
275 assert gl_error
== gl_NO_ERROR
else print_error gl_error
278 glBindTexture
(gl_TEXTURE_2D
, texture
)
279 glTexParameteri
(gl_TEXTURE_2D
, gl_TEXTURE_MIN_FILTER
, gl_LINEAR
)
280 glTexParameteri
(gl_TEXTURE_2D
, gl_TEXTURE_MAG_FILTER
, gl_LINEAR
)
281 glTexParameteri
(gl_TEXTURE_2D
, gl_TEXTURE_WRAP_S
, gl_CLAMP_TO_EDGE
)
282 glTexParameteri
(gl_TEXTURE_2D
, gl_TEXTURE_WRAP_T
, gl_CLAMP_TO_EDGE
)
283 glTexImage2D
(gl_TEXTURE_2D
, 0, gl_RGB
, width
, height
,
284 0, gl_RGB
, gl_UNSIGNED_BYTE
, new Pointer.nul
)
285 glFramebufferTexture2D
(gl_FRAMEBUFFER
, gl_COLOR_ATTACHMENT0
, gl_TEXTURE_2D
, texture
, 0)
287 gl_error
= glGetError
288 assert gl_error
== gl_NO_ERROR
else print_error gl_error
291 glBindRenderbuffer
(gl_RENDERBUFFER
, 0)
292 glBindFramebuffer
(gl_FRAMEBUFFER
, 0)
294 gl_error
= glGetError
295 assert gl_error
== gl_NO_ERROR
else print_error gl_error
298 var destroyed
= false
301 if destroyed
then return
305 glDeleteBuffers
([buffer_array
])
306 var gl_error
= glGetError
307 assert gl_error
== gl_NO_ERROR
else print_error gl_error
310 # Free the dynamic framebuffer and its attachments
311 glDeleteBuffers
([buffer_array
])
312 glDeleteFramebuffers
([dynamic_framebuffer
])
313 glDeleteRenderbuffers
([depth_renderbuffer
])
314 glDeleteTextures
([texture
])
318 private class DynamicResolutionProgram
319 super GamnitProgramFromSource
321 redef var vertex_shader_source
= """
322 // Vertex coordinates
323 attribute vec3 coord;
325 // Vertex coordinates on textures
326 attribute vec2 tex_coord;
328 // Output to the fragment shader
329 varying vec2 v_coord;
333 gl_Position = vec4(coord, 1.0);
336 """ @ glsl_vertex_shader
338 redef var fragment_shader_source
= """
339 precision mediump float;
341 // Virtual screen texture / color attachment
342 uniform sampler2D texture0;
344 // Input from the vertex shader
345 varying vec2 v_coord;
347 // Ratio of the virtual screen to draw
352 gl_FragColor = texture2D(texture0, v_coord*ratio);
354 """ @ glsl_fragment_shader
356 # Vertices coordinates
357 var coord
= attributes
["coord"].as(AttributeVec3) is lazy
359 # Coordinates on the textures, per vertex
360 var tex_coord
= attributes
["tex_coord"].as(AttributeVec2) is lazy
362 # Virtual screen texture / color attachment
363 var texture
= uniforms
["texture0"].as(UniformSampler2D) is lazy
365 # Ratio of the virtual screen to draw
366 var ratio
= uniforms
["ratio"].as(UniformFloat) is lazy