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

Redefined classes

redef class App

gamnit :: dynamic_resolution $ App

App subclasses are cross-platform applications

All class definitions

redef class App

gamnit :: dynamic_resolution $ App

App subclasses are cross-platform applications
package_diagram gamnit::dynamic_resolution dynamic_resolution performance_analysis performance_analysis gamnit::dynamic_resolution->performance_analysis gamnit gamnit gamnit::dynamic_resolution->gamnit realtime realtime performance_analysis->realtime ...realtime ... ...realtime->realtime ...gamnit ... ...gamnit->gamnit gamnit::flat_core flat_core gamnit::flat_core->gamnit::dynamic_resolution gamnit::font font gamnit::font->gamnit::flat_core gamnit::depth_core depth_core gamnit::depth_core->gamnit::flat_core gamnit::font... ... gamnit::font...->gamnit::font gamnit::depth_core... ... gamnit::depth_core...->gamnit::depth_core

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module app

app :: app

app.nit is a framework to create cross-platform applications
module app_base

app :: app_base

Base of the app.nit framework, defines App
module array

core :: array

This module introduces the standard array structure.
module assets

app :: assets

Portable services to load resources from the assets folder
module aware

android :: aware

Android compatibility module
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module c

c :: c

Structures and services for compatibility with the C language
module caching

serialization :: caching

Services for caching serialization engines
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module display

gamnit :: display

Abstract display services
module engine_tools

serialization :: engine_tools

Advanced services for serialization engines
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module glesv2

glesv2 :: glesv2

OpenGL graphics rendering library for embedded systems, version 2.0
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module input

mnit :: input

Defines abstract classes for user and general inputs to the application.
module inspect

serialization :: inspect

Refine Serializable::inspect to show more useful information
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module matrix

matrix :: matrix

Services for matrices of Float values
module meta

meta :: meta

Simple user-defined meta-level to manipulate types of instances as object.
module more_collections

more_collections :: more_collections

Highly specific, but useful, collections-related classes.
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module poset

poset :: poset

Pre order sets and partial order set (ie hierarchies)
module programs

gamnit :: programs

Services for graphical programs with shaders, attributes and uniforms
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module realtime

realtime :: realtime

Services to keep time of the wall clock time
module ropes

core :: ropes

Tree-based representation of a String.
module serialization

serialization :: serialization

General serialization services
module serialization_core

serialization :: serialization_core

Abstract services to serialize Nit objects to different formats
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module textures

gamnit :: textures

Load textures, create subtextures and manage their life-cycle
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module gamnit

gamnit :: gamnit

Game and multimedia framework for Nit
module performance_analysis

performance_analysis :: performance_analysis

Services to gather information on the performance of events by categories

Children

module flat_core

gamnit :: flat_core

Core services for the flat API for 2D games

Descendants

module a_star-m

a_star-m

module bmfont

gamnit :: bmfont

Parse Angel Code BMFont format and draw text
module cardboard

gamnit :: cardboard

Update the orientation of world_camera at each frame using the head position given by android::cardboard
module depth

gamnit :: depth

Framework for 3D games in Nit
module depth_core

gamnit :: depth_core

Base entities of the depth 3D game framework
module flat

gamnit :: flat

Simple API for 2D games, built around Sprite and App::update
module font

gamnit :: font

Abstract font drawing services, implemented by bmfont and tileset
module model_dimensions

gamnit :: model_dimensions

Dimensions related services for Model and Mesh
module more_lights

gamnit :: more_lights

More implementations of Light
module more_materials

gamnit :: more_materials

Various material implementations
module more_meshes

gamnit :: more_meshes

More simple geometric meshes
module more_models

gamnit :: more_models

Services to load models from the assets folder
module particles

gamnit :: particles

Particle effects
module selection

gamnit :: selection

Select Actor from a screen coordinate
module shadow

gamnit :: shadow

Shadow mapping using a depth texture
module stereoscopic_view

gamnit :: stereoscopic_view

Refine EulerCamera and App::frame_core_draw to get a stereoscopic view
module tileset

gamnit :: tileset

Support for TileSet, TileSetFont and drawing text with TextSprites
module virtual_gamepad

gamnit :: virtual_gamepad

Virtual gamepad mapped to keyboard keys for quick and dirty mobile support
module vr

gamnit :: vr

VR support for gamnit depth, for Android only
# 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 is writable

	# 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

	# Real screen framebuffer
	private var screen_framebuffer_cache: Int = -1

	# Real screen framebuffer name
	fun screen_framebuffer: Int
	do
		var cache = screen_framebuffer_cache
		if cache != -1 then return cache

		cache = glGetIntegerv(gl_FRAMEBUFFER_BINDING, 0)
		self.screen_framebuffer_cache = cache
		return cache
	end

	redef fun create_gamnit
	do
		super

		var program = dynres_program
		program.compile_and_link
		var error = program.error
		assert error == null else print_error error

		dynamic_context_cache = null
	end

	redef fun on_resize(display)
	do
		if dynamic_context_cache != null then dynamic_context.resize(display, max_dynamic_resolution_ratio)
		super
	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
			bind_screen_framebuffer screen_framebuffer
			glViewport(0, 0, display.width, display.height)
			glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT

			assert glGetError == gl_NO_ERROR
			return
		end

		# Draw to our dynamic framebuffer
		glBindFramebuffer(gl_FRAMEBUFFER, dynamic_context.dynamic_framebuffer)

		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)

		glClear gl_COLOR_BUFFER_BIT | gl_DEPTH_BUFFER_BIT

		assert glGetError == gl_NO_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)

		bind_screen_framebuffer 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)
		assert glGetError == gl_NO_ERROR

		# Draw
		glDrawArrays(gl_TRIANGLE_STRIP, 0, 4)
		assert glGetError == gl_NO_ERROR

		# Take down
		glBindBuffer(gl_ARRAY_BUFFER, 0)
		assert glGetError == gl_NO_ERROR

		sys.perfs["gamnit flat dyn res"].add app.perf_clock_dynamic_resolution.lapse
	end

	# Framebuffer and texture for dynamic resolution intermediate drawing
	private fun dynamic_context: DynamicContext
	do
		var cache = dynamic_context_cache
		if cache != null then return cache

		cache = create_dynamic_context
		dynamic_context_cache = cache
		return cache
	end

	private var dynamic_context_cache: nullable DynamicContext = null

	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

# Handles to reused GL buffers and texture
private class DynamicContext

	# 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

	# Prepare all attributes once per resolution change
	fun prepare_once(display: GamnitDisplay, max_dynamic_resolution_ratio: Float)
	do
		# TODO enable antialiasing.

		# Framebuffer
		var framebuffer = glGenFramebuffers(1).first
		glBindFramebuffer(gl_FRAMEBUFFER, framebuffer)
		assert glIsFramebuffer(framebuffer)
		self.dynamic_framebuffer = framebuffer
		assert glGetError == gl_NO_ERROR

		# Depth & texture/color
		var depthbuffer = glGenRenderbuffers(1).first
		self.depth_renderbuffer = depthbuffer
		var texture = glGenTextures(1).first
		self.texture = texture
		assert glGetError == gl_NO_ERROR

		resize(display, max_dynamic_resolution_ratio)
		assert glCheckFramebufferStatus(gl_FRAMEBUFFER) == gl_FRAMEBUFFER_COMPLETE

		# Array buffer
		buffer_array = glGenBuffers(1).first
		glBindBuffer(gl_ARRAY_BUFFER, buffer_array)
		assert glIsBuffer(buffer_array)
		assert glGetError == gl_NO_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)

		assert glGetError == gl_NO_ERROR
	end

	# Init size or resize `depth_renderbuffer` and `texture`
	fun resize(display: GamnitDisplay, max_dynamic_resolution_ratio: Float)
	do
		var width = (display.width.to_f * max_dynamic_resolution_ratio).to_i
		var height = (display.height.to_f * max_dynamic_resolution_ratio).to_i

		glBindFramebuffer(gl_FRAMEBUFFER, dynamic_framebuffer)

		var depthbuffer = self.depth_renderbuffer
		var texture = self.texture

		# Depth
		glBindRenderbuffer(gl_RENDERBUFFER, depthbuffer)
		assert glIsRenderbuffer(depthbuffer)
		glRenderbufferStorage(gl_RENDERBUFFER, gl_DEPTH_COMPONENT16, width, height)
		glFramebufferRenderbuffer(gl_FRAMEBUFFER, gl_DEPTH_ATTACHMENT, gl_RENDERBUFFER, depthbuffer)
		assert glGetError == gl_NO_ERROR

		# Texture
		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)

		assert glGetError == gl_NO_ERROR

		# Take down
		glBindRenderbuffer(gl_RENDERBUFFER, 0)
		glBindFramebuffer(gl_FRAMEBUFFER, 0)

		assert glGetError == gl_NO_ERROR
	end

	var destroyed = false
	fun destroy
	do
		if destroyed then return
		destroyed = true

		# Free the buffer
		glDeleteBuffers([buffer_array])
		assert glGetError == gl_NO_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
lib/gamnit/dynamic_resolution.nit:15,1--354,3