myou-engine/src/myou_engine.nim

213 lines
7.9 KiB
Nim

# The contents of this file are subject to the Common Public Attribution License
# Version 1.0 (the “License”); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
# https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla
# Public License Version 1.1 but Sections 14 and 15 have been added to cover use
# of software over a computer network and provide for limited attribution for
# the Original Developer. In addition, Exhibit A has been modified to be
# consistent with Exhibit B.
#
# Software distributed under the License is distributed on an “AS IS” basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
# the specific language governing rights and limitations under the License.
#
# The Original Code is Myou Engine.
#
# the Original Developer is the Initial Developer.
#
# The Initial Developer of the Original Code is the Myou Engine developers.
# All portions of the code written by the Myou Engine developers are Copyright
# (c) 2024. All Rights Reserved.
#
# Alternatively, the contents of this file may be used under the terms of the
# GNU Affero General Public License version 3 (the [AGPL-3] License), in which
# case the provisions of [AGPL-3] License are applicable instead of those above.
#
# If you wish to allow use of your version of this file only under the terms of
# the [AGPL-3] License and not to allow others to use your version of this file
# under the CPAL, indicate your decision by deleting the provisions above and
# replace them with the notice and other provisions required by the [AGPL-3]
# License. If you do not delete the provisions above, a recipient may use your
# version of this file under either the CPAL or the [AGPL-3] License.
## This module containst the functions to start using Myou Engine.
# TODO: runnableExamples is broken because we can't pass compile options
## ```nim
## let engine = newMyouEngine(1024, 768, "My Game")
##
## # Create or load your initial scene here
##
## engine.run()
## ```
import ./types
import std/tables
when defined(android):
const default_gl_version = 320
const default_gl_es = true
elif defined(emscripten) or defined(ios):
const default_gl_version = 300
const default_gl_es = true
else:
const default_gl_version = 330
const default_gl_es = false
# Forward declarations
proc newMyouEngine*(
width, height: int32,
title = "Myou Engine window",
opengl_version = default_gl_version,
opengl_es = default_gl_es,
glsl_version = "",
use_glsl_tone_mapping = true,
): MyouEngine
proc get_builtin_shader_library*(use_cubemap_prefiltering = true): string
proc get_builtin_shader_textures*(): Table[string, Texture]
# End forward declarations
import std/strutils
import std/monotimes
import ./graphics/render
import ./screen
import ./platform/platform
import ./loaders/blend
import ./util
from loadable import updateLoadableWorkerThreads
import arr_ref
export arr_ref
export tables
proc newMyouEngine*(
width, height: int32,
title = "Myou Engine window",
opengl_version = default_gl_version,
opengl_es = default_gl_es,
glsl_version = "",
use_glsl_tone_mapping = true,
): MyouEngine =
## Creates a Myou Engine instance. You need to call this before you can use
## the engine. You also need to call `run <#run,MyouEngine>`_ at the end of
## your main project file.
result = new MyouEngine
result.glsl_version = if glsl_version != "":
glsl_version
elif opengl_es:
$opengl_version & " es"
else:
$opengl_version
# TODO: move/copy to camera or to scene?
# to override it with per-camera exposure settings
if opengl_es:
assert opengl_version >= 300, "Minimum supported OpenGL ES version is 3.0"
else:
assert opengl_version >= 330, "Minimum supported OpenGL version is 3.3"
if opengl_es or use_glsl_tone_mapping:
result.tone_mapping_library = dedent """
float linearrgb_to_srgb(float c){
if (c < 0.0031308) return (c < 0.0) ? 0.0 : c * 12.92;
else return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
}
vec4 linearrgb_to_srgb(vec4 col){
return vec4(
linearrgb_to_srgb(col.r),
linearrgb_to_srgb(col.g),
linearrgb_to_srgb(col.b),
col.a
);
}
#define MYOU_TONE_MAP(x) linearrgb_to_srgb(x)
"""
result.tone_mapping_function = "MYOU_TONE_MAP"
result.use_glsl_tone_mapping = true
else:
result.tone_mapping_library = "\n#define MYOU_TONE_MAP(x) x\n"
echo "assigning renderer"
result.renderer = result.newRenderManager
# this will call result.renderer.initialize() now or later
# but first it will ensure a screen can be created
when not defined(nimdoc):
init_graphics(result, width, height, title, opengl_version, opengl_es)
discard result.newScreen(width, height, title)
registerBlendLoader(result)
proc get_builtin_shader_library*(use_cubemap_prefiltering = true): string =
## Returns a string with the code of the default shader library of the
## engine. If you use this, you may want to also add the textures given by
## `get_builtin_shader_textures<#get_builtin_shader_textures>`_.
return @[
if use_cubemap_prefiltering:
"#define PREFILTERED_CUBEMAPS"
else: "",
# staticOrDebugRead "shaders/debug_text.glsl",
staticOrDebugRead "shaders/cube_prefilter.glsl",
staticOrDebugRead "shaders/spherical_harmonics.glsl",
staticOrDebugRead "shaders/shader_library.glsl",
].join("\n")
proc get_builtin_shader_textures*(): Table[string, Texture] =
## Returns a table of textures to be used with the library that is returned
## by `get_builtin_shader_library<#get_builtin_shader_library,bool>`_.
discard
var last_time: float
proc myou_main_loop*(self: MyouEngine) =
## Runs one iteration of the engine main loop. It doesn't swap buffers.
##
## You usually don't need to call this. Use `run <#run,MyouEngine>`_
## instead.
updateLoadableWorkerThreads()
# TODO: make a table object that can be iterated while changing, e.g. with a
# seq and a dirty flag to update the seq
if self.new_scenes.len != 0:
for name,scene in self.new_scenes.pairs:
self.scenes[name] = scene
self.new_scenes.clear()
let time = getmonotime().ticks.float/1000000000
let delta_seconds = time - last_time
for _,scene in self.scenes.pairs:
if not scene.enabled:
continue
for f in scene.pre_draw_callbacks:
f(scene, delta_seconds)
self.renderer.draw_all()
for _,scene in self.scenes.pairs:
if not scene.enabled:
continue
for f in scene.post_draw_callbacks:
f(scene, delta_seconds)
last_time = time
proc run*(self: MyouEngine) =
## Starts the main loop of the engine. You should call it at the end of your
## main file. In mobile platforms and on web, this function doesn't block,
## and instead it just configures the main loop function. Therefore you
## shouldn't run any code after calling it.
last_time = getmonotime().ticks.float/1000000000
when not defined(nimdoc):
start_platform_main_loop(self, myou_main_loop)
proc loadScene*(self: MyouEngine, uri: string, callback: proc(scene: Scene), name = "", ext = "") =
let ext = if ext == "":
uri.rsplit('.', 1)[1]
else:
ext
if ext notin self.loaders_by_ext:
raise ValueError.newException "File extension not supported: " & ext
# TODO: use the next loader if the first one fails
let loaders = self.loaders_by_ext[ext]
let loader = loaders[0](self)
loader.openAssetFile(uri)
loader.loadScene(name, nil, callback)