Archived
1
Fork 0

Add initial files

This commit is contained in:
redstrate 2020-08-11 12:07:21 -04:00
parent 6dfe6263c5
commit 4b642fcb66
1137 changed files with 446859 additions and 2 deletions

74
.gitignore vendored Normal file
View file

@ -0,0 +1,74 @@
extern/SPIRV-Cross/external/
build/
*build/
.DS_Store
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Gcc Patch
/*.gcno
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app

137
CMakeLists.txt Executable file
View file

@ -0,0 +1,137 @@
cmake_minimum_required(VERSION 3.17)
project(PrismEngine
LANGUAGES CXX)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
# enable folders in IDEs that support this feature
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/Common.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/AddPlatformExecutable.cmake)
include(FetchContent)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
message("Windows build detected!")
set(ENABLE_WINDOWS ON)
set(ENABLE_VULKAN ON)
set(ENABLE_OPENGL ON)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "WindowsStore")
message("UWP build detected!")
set(ENABLE_UWP TRUE)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" AND NOT IOS)
message("macOS build detected!")
set(ENABLE_METAL TRUE)
set(ENABLE_DARWIN TRUE)
set(ENABLE_MACOS TRUE)
set(CMAKE_XCODE_GENERATE_SCHEME OFF)
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
message("Linux build detected!")
set(ENABLE_VULKAN TRUE)
set(ENABLE_LINUX TRUE)
endif()
FetchContent_Declare(
lua
GIT_REPOSITORY https://github.com/NLua/lua.git
GIT_TAG master
)
FetchContent_Declare(
bullet
GIT_REPOSITORY https://github.com/bulletphysics/bullet3.git
GIT_TAG 2.89
)
FetchContent_Declare(
spirv-cross
GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Cross.git
GIT_TAG 2020-05-19
)
FetchContent_Declare(
glslang
GIT_REPOSITORY https://github.com/KhronosGroup/glslang.git
GIT_TAG master
)
macro(manual_download)
set(BUILD_BULLET3 OFF CACHE BOOL "" FORCE)
set(BUILD_BULLET3_DEMOS OFF CACHE BOOL "" FORCE)
set(BUILD_BULLET2_DEMOS OFF CACHE BOOL "" FORCE)
set(BUILD_CPU_DEMOS OFF CACHE BOOL "" FORCE)
set(USE_GRAPHICAL_BENCHMARK OFF CACHE BOOL "" FORCE)
set(BUILD_EXTRAS OFF CACHE BOOL "" FORCE)
set(INSTALL_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_UNIT_TESTS OFF CACHE BOOL "" FORCE)
set(SPIRV_CROSS_SKIP_INSTALL ON CACHE BOOL "" FORCE)
set(BUILD_EXTERNAL OFF CACHE BOOL "" FORCE)
set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "" FORCE)
add_definitions(-DLUA_USE_APPLE)
set(CMAKE_FOLDER "External")
FetchContent_MakeAvailable(lua)
FetchContent_MakeAvailable(bullet)
FetchContent_MakeAvailable(spirv-cross)
FetchContent_MakeAvailable(glslang)
remove_definitions(LUA_USE_MACOSX)
set(CMAKE_FOLDER "")
endmacro()
if(${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
message("iOS build detected!")
set(ENABLE_METAL TRUE)
set(ENABLE_DARWIN TRUE)
set(ENABLE_IOS TRUE)
manual_download()
endif()
if(${CMAKE_SYSTEM_NAME} STREQUAL "tvOS")
message("tvOS build detected!")
set(ENABLE_METAL TRUE)
set(ENABLE_DARWIN TRUE)
set(ENABLE_TVOS TRUE)
manual_download()
endif()
add_subdirectory(extern)
add_subdirectory(platforms)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
add_subdirectory(engine)
set(CMAKE_FOLDER "Tools" PARENT_SCOPE)
if(NOT IOS AND NOT ENABLE_TVOS)
add_subdirectory(tools/shadercompiler)
endif()
if(BUILD_TOOLS)
add_subdirectory(tools/common)
add_subdirectory(tools/fontcompiler)
add_subdirectory(tools/editor)
add_subdirectory(tools/modelcompiler)
add_subdirectory(tools/cutsceneeditor)
endif()
set(CMAKE_FOLDER "" PARENT_SCOPE)

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Joshua Goins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,2 +1,54 @@
# prism
Cross-platform 3D game engine featuring real-time physically based rendering
# Prism
A cross-platform game engine that integrates a real-time physically based workflow and makes it easy to get started writing games or other graphical applications in C++!
I've been using this to actually develop a game i've been working on, and includes multiple systems to facilitate gameplay (like the UI and audio systems) along with your usual engine systems (like Renderer, and Log). However, the same engine is also proven to be useful for other graphical applications, like the Prism editor.
Although I developed this on macOS, it has been tested on Windows, Linux, iOS, iPadOS and tvOS machines. Apart from some platform glue, these share a majority of the same code!
## Features
Here is a list of some of the notable features of Prism:
* Cross platform graphics API is used for rendering
* Vulkan and Metal backends available
* Shaders are written in GLSL, and compiled to SPIR-V or MSL offline and at runtime using SPIRV-Cross.
* Windowing and System abstraction
* main() and other platform-specific tidbits are abstracted as well, for easy support on non-desktop platforms.
* HiDPI is also supported!
* If available, support for multiple windows as well.
* Dear ImGui used for debug/editor UIs
* Includes the new docking/viewport branch!
* Automatic DPI scaling on fonts for crisp rendering when the window is in a HiDPI environment.
* Custom backend built on top the GFX api and other platform agnostic systems.
* Plenty of custom widgets available for easy debug tooling, see imgui_stdlib.h and imgui_utility.hpp!
* Entity-Component system for scene authoring
* No runtime polymorphism is involved and leverages the native C++ type system. Components are simple structs.
* Asset management
* Custom model pipeline allowing for blazingly fast model loading and authoring.
* Includes a custom blender addon for easy export!
* Assets are referenced from disk, and only loaded once - unloaded once they are no longer referenced.
* Thumbnails are created by the editor and stored on disk until further use.
* Custom UI system for easy authoring
* UIs are declared in JSON, and there is a graphical editor available as well.
* They can even be placed in the world with the UI component!
* Custom math library
* Quaternions, matrices, vectors, rays, and more are available!
* Infinite perspective matrices are created by default.
* Advanced rendering techniques
* Rendering is consistent regardless of platform or API used!
* Where older or underperforming GPUs are supported (for example, an Apple TV 4K, or a Thinkpad X230), the engine supports disabling expensive features like IBL or point light shadows along with other scalability options.
* Real-time physically based rendering including scene probes used for image-based lighting.
* Material node editor for easy and fast material authoring.
* Skinned mesh support with animation.
* PCSS for soft shadows, supported on every type of light.
* Editors supporting scene, cutscene, material authoring
* Multidocument workflow similar to UE4.
* Transform handles for easy object manipulation.
## Usage
### Requirements
* CMake
* Support for Vulkan or Metal
* C++ compiler that fully supports C++17
* MSVC, Clang, and GCC have been tested
There is no example available, but if you clone this repository and include it in your cmake tree, use the function `add_platform_executable` from AddPlatformExecutable.cmake to create a new Prism app.

226
addon/addon.py Normal file
View file

@ -0,0 +1,226 @@
bl_info = {
"name": "Prism Asset Pipeline",
"blender": (2, 80, 0),
"location": "File > Import-Export",
"category": "Import-Export"
}
import bpy
from bpy.types import Operator, AddonPreferences
from bpy.props import StringProperty, BoolProperty, PointerProperty
from bpy_extras.io_utils import (
ExportHelper,
)
import subprocess
class MarkExportOperator(bpy.types.Operator):
bl_idname = "scene.mark_export_operator"
bl_label = "Mark All Objects for Export"
def execute(self, context):
for ob in context.scene.objects:
if ob.type == 'MESH':
ob.willExport = True
return {'FINISHED'}
class HelloWorldPanel(bpy.types.Panel):
bl_idname = "OBJECT_PT_hello_world"
bl_label = "Prism Asset Pipeline"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "object"
def draw(self, context):
self.layout.prop(context.object, "modelContainer", text="Model Group")
self.layout.prop(context.object, "willExport", text="Will Export")
self.layout.prop(context.object, "exportArmature", text="Export Armature")
self.layout.prop(context.object, "exportAnim", text="Export Animations")
class ExportSettingsPanel(bpy.types.Panel):
bl_idname = "OBJECT_PT_prism_srttings"
bl_label = "Prism Asset Pipeline"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
def draw(self, context):
self.layout.operator("scene.mark_export_operator")
self.layout.prop(context.scene, "export_prefix", text="Export Prefix")
self.layout.prop(context.scene, "removeTransforms", text="Remove Transforms")
def console_get():
for area in bpy.context.screen.areas:
if area.type == 'CONSOLE':
for space in area.spaces:
if space.type == 'CONSOLE':
return area, space
return None, None
def console_write(text):
area, space = console_get()
if space is None:
return
context = bpy.context.copy()
context.update(dict(
space=space,
area=area,
))
for line in text.split("\n"):
bpy.ops.console.scrollback_append(context, text=line, type='OUTPUT')
class ExampleAddonPreferences(AddonPreferences):
bl_idname = __name__
def update_func(self, context):
if "//" in self.modelCompilerPath:
self.modelCompilerPath = bpy.path.abspath(self.modelCompilerPath)
if "//" in self.dataExportPath:
self.dataExportPath = bpy.path.abspath(self.dataExportPath)
modelCompilerPath = StringProperty(
name="Model Compiler Path",
subtype='FILE_PATH',
update=update_func
)
dataExportPath = StringProperty(
name="Data Export Path",
subtype='DIR_PATH',
update=update_func
)
def draw(self, context):
layout = self.layout
layout.prop(self, "modelCompilerPath")
layout.prop(self, "dataExportPath")
class ExportAssetPipeline(bpy.types.Operator):
bl_idname = "prism.export_pipeline"
bl_label = "Export Prism Engine"
action = bpy.props.StringProperty(default="EVERYTHING")
def execute(self, context):
preferences = context.preferences
addon_prefs = preferences.addons[__name__].preferences
if len(addon_prefs.modelCompilerPath) == 0 or len(addon_prefs.dataExportPath) == 0:
console_write("No modelS compiler or export path!")
return {'FINISHED'}
model_compiler = bpy.path.abspath(addon_prefs.modelCompilerPath)
export_groups = {}
for object in context.scene.objects:
if object.willExport:
if not object.modelContainer in export_groups:
export_groups[object.modelContainer] = {}
export_groups[object.modelContainer]["objects"] = []
console_write("exporting " + object.name + " as " + object.modelContainer)
export_groups[object.modelContainer]["objects"].append(object.name)
if object.exportArmature is not None:
export_groups[object.modelContainer]["armature"] = object.exportArmature.name
console_write(str(export_groups))
for group in export_groups.keys():
bpy.ops.object.select_all(action='DESELECT')
will_export_anim = False
if "armature" in export_groups[group]:
object = context.scene.objects[export_groups[group]["armature"]]
object.select_set(state=True)
for object_name in export_groups[group]["objects"]:
object = context.scene.objects[object_name]
if object.exportAnim == True:
will_export_anim = True
object.select_set(state=True)
name_part = context.scene.export_prefix + group
fbx_path = "//" + name_part + ".fbx"
fbx_path = bpy.path.abspath(fbx_path)
if self.action == 'ONLY_MESH':
will_export_anim = False
options_dict = {'filepath': fbx_path,
'check_existing': False,
'use_selection': True,
'bake_anim': will_export_anim,
'apply_unit_scale': True,
'add_leaf_bones': False,
'use_armature_deform_only': True,
'mesh_smooth_type': 'OFF',
'bake_space_transform': context.scene.removeTransforms,
'use_tspace': True
}
if context.scene.removeTransforms:
#options_dict['bake_space_transform'] =True
bpy.ops.export_scene.fbx(**options_dict)
else:
bpy.ops.export_scene.fbx(**options_dict)
args = [model_compiler, "--no_ui", "--model-path", fbx_path, "--data-path", bpy.path.abspath(addon_prefs.dataExportPath)]
if context.scene.removeTransforms:
args.append("--compile-static")
p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
output, errors = p.communicate()
return {'FINISHED'}
class PrismExportSubMenu(bpy.types.Menu):
bl_idname = "OBJECT_MT_prism_export_submenu"
bl_label = "Prism Asset Pipeline"
def draw(self, context):
self.layout.operator(ExportAssetPipeline.bl_idname, text="Everything").action = 'EVERYTHING'
self.layout.operator(ExportAssetPipeline.bl_idname, text="Only Meshes").action = 'ONLY_MESH'
def menu_func_export(self, context):
self.layout.menu(PrismExportSubMenu.bl_idname)
def register():
bpy.utils.register_class(HelloWorldPanel)
bpy.utils.register_class(ExportSettingsPanel)
bpy.utils.register_class(ExportAssetPipeline)
bpy.utils.register_class(MarkExportOperator)
bpy.utils.register_class(PrismExportSubMenu)
# properties
bpy.types.Object.modelContainer = StringProperty()
bpy.types.Object.willExport = BoolProperty()
bpy.types.Object.exportAnim = BoolProperty()
bpy.types.Scene.export_prefix = StringProperty()
bpy.types.Scene.removeTransforms = BoolProperty()
bpy.types.Object.exportArmature = PointerProperty(type=bpy.types.Object)
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
bpy.utils.register_class(ExampleAddonPreferences)
def unregister():
bpy.utils.unregister_class(HelloWorldPanel)
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
bpy.utils.unregister_class(ExportAssetPipeline)
bpy.utils.unregister_class(ExampleAddonPreferences)
bpy.utils.unregister_class(MarkExportOperator)
if __name__ == "__main__":
register()

View file

@ -0,0 +1,70 @@
function(set_output_dir target)
set_target_properties(${target} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
XCODE_GENERATE_SCHEME ON)
endfunction()
function(add_platform_executable)
cmake_parse_arguments(add_platform_executable
""
"TARGET;APP_CLASS;APP_INCLUDE;APP_NAME;SKIP_DATA"
"SRC"
${ARGN})
set(APP_CLASS ${add_platform_executable_APP_CLASS} CACHE INTERNAL "")
set(GAME_SRC ${add_platform_executable_SRC} CACHE INTERNAL "")
set(APP_INCLUDE ${add_platform_executable_APP_INCLUDE} CACHE INTERNAL "")
set(APP_NAME ${add_platform_executable_APP_NAME} CACHE INTERNAL "")
string(REGEX MATCH "^(.*)\\.[^.]*$" dummy ${PLATFORM_MAIN_FILE_NAME})
set(MYFILE_WITHOUT_EXT ${CMAKE_MATCH_1})
configure_file(${PLATFORM_MAIN_FILE_PATH} ${CMAKE_BINARY_DIR}/${add_platform_executable_TARGET}${MYFILE_WITHOUT_EXT})
if(COMMAND setup_target)
setup_target(${add_platform_executable_TARGET})
endif()
add_executable(${add_platform_executable_TARGET}
${PLATFORM_SOURCES}
${add_platform_executable_SRC}
${CMAKE_BINARY_DIR}/${add_platform_executable_TARGET}${MYFILE_WITHOUT_EXT})
if("${PLATFORM_EXECUTABLE_PROPERTIES}" STREQUAL "")
else()
set_target_properties(${add_platform_executable_TARGET}
PROPERTIES
${PLATFORM_EXECUTABLE_PROPERTIES}
)
endif()
target_link_libraries(${add_platform_executable_TARGET}
PRIVATE
${PLATFORM_LINK_LIBRARIES}
)
if("${add_platform_executable_APP_NAME}")
set_target_properties(${add_platform_executable_TARGET} PROPERTIES XCODE_ATTRIBUTE_EXECUTABLE_NAME ${add_platform_executable_APP_NAME})
set_target_properties(${add_platform_executable_TARGET} PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME ${add_platform_executable_APP_NAME})
endif()
if(COMMAND add_platform_commands)
add_platform_commands(${add_platform_executable_TARGET})
endif()
set_output_dir(${add_platform_executable_TARGET})
endfunction()
function(add_platform)
cmake_parse_arguments(add_platform
"$"
"MAIN_FILE;DISPLAY_NAME"
"EXECUTABLE_PROPERTIES;LINK_LIBRARIES;COMPILE_OPTIONS;SRC"
${ARGN})
set(PLATFORM_MAIN_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/${add_platform_MAIN_FILE} CACHE INTERNAL "")
set(PLATFORM_MAIN_FILE_NAME ${add_platform_MAIN_FILE} CACHE INTERNAL "")
set(PLATFORM_LINK_LIBRARIES ${add_platform_LINK_LIBRARIES} CACHE INTERNAL "")
set(PLATFORM_EXECUTABLE_PROPERTIES ${add_platform_EXECUTABLE_PROPERTIES} CACHE INTERNAL "")
set(PLATFORM_SOURCES ${add_platform_SRC} CACHE INTERNAL "")
endfunction()

44
cmake/BuildShaders.cmake Executable file
View file

@ -0,0 +1,44 @@
macro(compile_shader src)
string(REGEX REPLACE "\\.[^.]*$" "" MYFILE_WITHOUT_EXT ${src})
set(SHADER_COMPILER_COMMAND "${CMAKE_BINARY_DIR}/bin/Debug/ShaderCompiler")
if(ENABLE_IOS)
set(SHADER_COMPILER_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/../build/bin/Debug/ShaderCompiler")
endif()
set(EXTRA_PLATFORM_ARG "0")
if(ENABLE_IOS)
set(EXTRA_PLATFORM_ARG "1")
endif()
add_custom_command(
OUTPUT ${CMAKE_BINARY_DIR}/${MYFILE_WITHOUT_EXT}.glsl
COMMAND ${SHADER_COMPILER_COMMAND} ${CMAKE_CURRENT_SOURCE_DIR}/../../${src} ${CMAKE_BINARY_DIR}/${src} ${EXTRA_PLATFORM_ARG}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../${src}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../../shaders
)
endmacro()
function(add_shader)
if(NOT ENABLE_IOS)
set(options OPTIONAL FAST)
set(oneValueArgs TARGET)
set(multiValueArgs SHADERS)
cmake_parse_arguments(add_shader "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
foreach(shader ${add_shader_SHADERS})
string(REGEX REPLACE "\\.[^.]*$" "" MYFILE_WITHOUT_EXT ${shader})
compile_shader(${shader})
list(APPEND SPV_FILES ${CMAKE_BINARY_DIR}/${MYFILE_WITHOUT_EXT}.glsl)
endforeach()
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/shaders)
add_custom_target(BuildShaders DEPENDS ${SPV_FILES} ShaderCompiler)
add_dependencies(${add_shader_TARGET} BuildShaders)
set(ALL_SHADER_FILES ${SPV_FILES} CACHE INTERNAL "" FORCE)
endif()
endfunction()

35
cmake/Common.cmake Executable file
View file

@ -0,0 +1,35 @@
macro(set_engine_properties target)
target_compile_features(${target} PUBLIC cxx_std_17)
set_target_properties(${target} PROPERTIES CXX_EXTENSIONS OFF)
target_compile_options(${target} PUBLIC
-Wall
-Wextra
-Wno-c++98-compat
-Wno-c++98-compat-pedantic
-Wno-padded
-Wno-documentation-unknown-command
-Wno-used-but-marked-unused
-Wno-system-headers
-Wconversion
-Wno-sign-conversion)
if(ENABLE_MACOS)
target_compile_definitions(${target} PUBLIC PLATFORM_MACOS)
endif()
if(ENABLE_WINDOWS)
target_compile_definitions(${target} PUBLIC PLATFORM_WINDOWS)
endif()
if(ENABLE_LINUX)
target_compile_definitions(${target} PUBLIC PLATFORM_LINUX)
endif()
if(ENABLE_IOS)
target_compile_definitions(${target} PUBLIC PLATFORM_IOS)
endif()
if(ENABLE_TVOS)
target_compile_definitions(${target} PUBLIC PLATFORM_TVOS)
endif()
endmacro()

50
cmake/FindLuaJIT.cmake Normal file
View file

@ -0,0 +1,50 @@
# Locate LuaJIT library
# This module defines
# LUAJIT_FOUND, if false, do not try to link to Lua
# LUA_INCLUDE_DIR, where to find lua.h
# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
#
# This module is similar to FindLua51.cmake except that it finds LuaJit instead.
FIND_PATH(LUA_INCLUDE_DIR luajit.h
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES include/luajit-2.1 include/luajit-2.0 include/luajit-5_1-2.1 include/luajit-5_1-2.0 include luajit
PATHS
~/Library/Frameworks
/Library/Frameworks
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt
)
FIND_LIBRARY(LUA_LIBRARY
NAMES luajit luajit-5.1
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES lib64 lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/sw
/opt/local
/opt/csw
/opt
)
IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/luajit.h")
FILE(STRINGS "${LUA_INCLUDE_DIR}/luajit.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"LuaJIT .+\"")
STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"LuaJIT ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
UNSET(lua_version_str)
ENDIF()
INCLUDE(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LUAJIT_FOUND to TRUE if
# all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(LuaJIT
REQUIRED_VARS LUA_LIBRARY LUA_INCLUDE_DIR
VERSION_VAR LUA_VERSION_STRING)
MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARY LUA_MATH_LIBRARY)

97
cmake/FindPortAudio.cmake Normal file
View file

@ -0,0 +1,97 @@
# - Try to find Portaudio
# Once done this will define
#
# PORTAUDIO_FOUND - system has Portaudio
# PORTAUDIO_INCLUDE_DIRS - the Portaudio include directory
# PORTAUDIO_LIBRARIES - Link these to use Portaudio
# PORTAUDIO_DEFINITIONS - Compiler switches required for using Portaudio
# PORTAUDIO_VERSION - Portaudio version
#
# Copyright (c) 2006 Andreas Schneider <mail@cynapses.org>
#
# Redistribution and use is allowed according to the terms of the New BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
if (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)
# in cache already
set(PORTAUDIO_FOUND TRUE)
else (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)
if (NOT WIN32)
include(FindPkgConfig)
pkg_check_modules(PORTAUDIO2 portaudio-2.0)
endif (NOT WIN32)
if (PORTAUDIO2_FOUND)
set(PORTAUDIO_INCLUDE_DIRS
${PORTAUDIO2_INCLUDE_DIRS}
)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(PORTAUDIO_LIBRARIES "${PORTAUDIO2_LIBRARY_DIRS}/lib${PORTAUDIO2_LIBRARIES}.dylib")
else (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(PORTAUDIO_LIBRARIES
${PORTAUDIO2_LIBRARIES}
)
endif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(PORTAUDIO_VERSION
19
)
set(PORTAUDIO_FOUND TRUE)
else (PORTAUDIO2_FOUND)
find_path(PORTAUDIO_INCLUDE_DIR
NAMES
portaudio.h
PATHS
/usr/include
/usr/local/include
/opt/local/include
/sw/include
)
find_library(PORTAUDIO_LIBRARY
NAMES
portaudio
PATHS
/usr/lib
/usr/local/lib
/opt/local/lib
/sw/lib
${PORTAUDIO_LIBRARY_DIR}
)
set(PORTAUDIO_INCLUDE_DIRS
${PORTAUDIO_INCLUDE_DIR}
)
set(PORTAUDIO_LIBRARIES
${PORTAUDIO_LIBRARY}
)
set(PORTAUDIO_LIBRARY_DIRS
${PORTAUDIO_LIBRARY_DIR}
)
set(PORTAUDIO_VERSION
18
)
if (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES)
set(PORTAUDIO_FOUND TRUE)
endif (PORTAUDIO_INCLUDE_DIRS AND PORTAUDIO_LIBRARIES)
if (PORTAUDIO_FOUND)
if (NOT Portaudio_FIND_QUIETLY)
message(STATUS "Found Portaudio: ${PORTAUDIO_LIBRARIES}")
endif (NOT Portaudio_FIND_QUIETLY)
else (PORTAUDIO_FOUND)
if (Portaudio_FIND_REQUIRED)
message(FATAL_ERROR "Could not find Portaudio")
endif (Portaudio_FIND_REQUIRED)
endif (PORTAUDIO_FOUND)
endif (PORTAUDIO2_FOUND)
# show the PORTAUDIO_INCLUDE_DIRS and PORTAUDIO_LIBRARIES variables only in the advanced view
mark_as_advanced(PORTAUDIO_INCLUDE_DIRS PORTAUDIO_LIBRARIES)
endif (PORTAUDIO_LIBRARIES AND PORTAUDIO_INCLUDE_DIRS)

77
cmake/Findassimp.cmake Normal file
View file

@ -0,0 +1,77 @@
include(FindPackageHandleStandardArgs)
if (WIN32)
# Find include files
find_path(
ASSIMP_INCLUDE_DIR
NAMES assimp/scene.h
PATHS
$ENV{PROGRAMFILES}/include
${ASSIMP_ROOT_DIR}/include
DOC "The directory where assimp/scene.h resides")
# Find library files
find_library(
ASSIMP_LIBRARY_RELEASE
NAMES assimp-vc140-mt
PATHS
$ENV{PROGRAMFILES}/lib
${ASSIMP_ROOT_DIR}/lib
${LM_EXTERNAL_LIBRARY_PATH}/Release)
find_library(
ASSIMP_LIBRARY_DEBUG
NAMES assimp-vc140-mtd
PATHS
$ENV{PROGRAMFILES}/lib
${ASSIMP_ROOT_DIR}/lib
${LM_EXTERNAL_LIBRARY_PATH}/Debug)
else()
# Find include files
find_path(
ASSIMP_INCLUDE_DIR
NAMES assimp/scene.h
PATHS
/usr/include
/usr/local/include
/sw/include
/opt/local/include
DOC "The directory where assimp/scene.h resides")
# Find library files
find_library(
ASSIMP_LIBRARY
NAMES assimp
PATHS
/usr/lib64
/usr/lib
/usr/local/lib64
/usr/local/lib
/sw/lib
/opt/local/lib
${ASSIMP_ROOT_DIR}/lib
DOC "The Assimp library")
endif()
if (WIN32)
# Handle REQUIRD argument, define *_FOUND variable
find_package_handle_standard_args(assimp DEFAULT_MSG ASSIMP_INCLUDE_DIR ASSIMP_LIBRARY_RELEASE ASSIMP_LIBRARY_DEBUG)
# Define ASSIMP_LIBRARIES and ASSIMP_INCLUDE_DIRS
if (ASSIMP_FOUND)
set(ASSIMP_LIBRARIES_RELEASE ${ASSIMP_LIBRARY_RELEASE})
set(ASSIMP_LIBRARIES_DEBUG ${ASSIMP_LIBRARY_DEBUG})
set(ASSIMP_LIBRARIES debug ${ASSIMP_LIBRARIES_DEBUG} optimized ${ASSIMP_LIBRARY_RELEASE})
set(ASSIMP_INCLUDE_DIRS ${ASSIMP_INCLUDE_DIR})
endif()
# Hide some variables
mark_as_advanced(ASSIMP_INCLUDE_DIR ASSIMP_LIBRARY_RELEASE ASSIMP_LIBRARY_DEBUG)
else()
find_package_handle_standard_args(assimp DEFAULT_MSG ASSIMP_INCLUDE_DIR ASSIMP_LIBRARY)
if (ASSIMP_FOUND)
set(ASSIMP_LIBRARIES ${ASSIMP_LIBRARY})
set(ASSIMP_INCLUDE_DIRS ${ASSIMP_INCLUDE_DIR})
endif()
mark_as_advanced(ASSIMP_INCLUDE_DIR ASSIMP_LIBRARY)
endif()

202
cmake/Findglslang.cmake Normal file
View file

@ -0,0 +1,202 @@
#.rst:
# Findglslang
# ----------
#
# Try to find glslang in the VulkanSDK
#
# IMPORTED Targets
# ^^^^^^^^^^^^^^^^
#
# This module defines :prop_tgt:`IMPORTED` target ``glslang::glslang``, if
# glslang has been found.
#
# Result Variables
# ^^^^^^^^^^^^^^^^
#
# This module defines the following variables::
#
# glslang_FOUND - True if glslang was found
# glslang_INCLUDE_DIRS - include directories for glslang
# glslang_LIBRARIES - link against this library to use glslang
#
# The module will also define two cache variables::
#
# glslang_INCLUDE_DIR - the glslang include directory
# glslang_LIBRARY - the path to the glslang library
#
if (DEFINED ENV{VULKAN_SDK})
if(WIN32)
set(ADDITIONAL_PATHS_INCLUDE "$ENV{VULKAN_SDK}/Include")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(ADDITIONAL_PATHS_LIBS
"$ENV{VULKAN_SDK}/Lib"
"$ENV{VULKAN_SDK}/Bin"
)
elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(ADDITIONAL_PATHS_LIBS
"$ENV{VULKAN_SDK}/Lib32"
"$ENV{VULKAN_SDK}/Bin32"
)
endif()
else()
set(ADDITIONAL_PATHS_INCLUDE "$ENV{VULKAN_SDK}/include")
set(ADDITIONAL_PATHS_LIBS "$ENV{VULKAN_SDK}/lib")
endif()
endif()
find_path(glslang_INCLUDE_DIR
NAMES glslang/Public/ShaderLang.h
PATHS ${ADDITIONAL_PATHS_INCLUDE}
)
find_path(spirv_INCLUDE_DIR
NAMES SPIRV/GlslangToSpv.h
PATHS ${ADDITIONAL_PATHS_INCLUDE}
)
find_library(glslang_LIBRARY
NAMES glslang
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(OSDependent_LIBRARY
NAMES OSDependent
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(SPIRV_LIBRARY
NAMES SPIRV
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(SPIRV-Tools_LIBRARY
NAMES SPIRV-Tools
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(SPIRV-Tools-opt_LIBRARY
NAMES SPIRV-Tools-opt
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(OGLCompiler_LIBRARY
NAMES OGLCompiler
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(HLSL_LIBRARY
NAMES HLSL
PATHS ${ADDITIONAL_PATHS_LIBS}
)
if(WIN32)
find_library(glslang_LIBRARY_debug
NAMES glslangd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(OSDependent_LIBRARY_debug
NAMES OSDependentd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(SPIRV_LIBRARY_debug
NAMES SPIRVd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(SPIRV-Tools_LIBRARY_debug
NAMES SPIRV-Toolsd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(SPIRV-Tools-opt_LIBRARY_debug
NAMES SPIRV-Tools-optd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(OGLCompiler_LIBRARY_debug
NAMES OGLCompilerd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
find_library(HLSL_LIBRARY_debug
NAMES HLSLd
PATHS ${ADDITIONAL_PATHS_LIBS}
)
endif()
set(glslang_LIBRARIES ${glslang_LIBRARY})
set(glslang_INCLUDE_DIRS ${glslang_INCLUDE_DIR})
mark_as_advanced(glslang_INCLUDE_DIR glslang_LIBRARY)
if(glslang_LIBRARY AND glslang_INCLUDE_DIR)
set(glslang_FOUND "YES")
message(STATUS "Found glslang: ${glslang_LIBRARY}")
else()
set(glslang_FOUND "NO")
message(STATUS "Failed to find glslang")
endif()
if(glslang_FOUND AND NOT TARGET glslang::glslang)
add_library(glslang::glslang UNKNOWN IMPORTED)
set_target_properties(glslang::glslang PROPERTIES IMPORTED_LOCATION "${glslang_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${glslang_INCLUDE_DIRS}")
add_library(glslang::OSDependent UNKNOWN IMPORTED)
set_target_properties(glslang::OSDependent PROPERTIES IMPORTED_LOCATION "${OSDependent_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${OSDependent_INCLUDE_DIRS}")
add_library(glslang::SPIRV UNKNOWN IMPORTED)
set_target_properties(glslang::SPIRV PROPERTIES IMPORTED_LOCATION "${SPIRV_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${spirv_INCLUDE_DIR}")
if (SPIRV-Tools_LIBRARY)
add_library(glslang::SPIRV-Tools UNKNOWN IMPORTED)
set_target_properties(glslang::SPIRV-Tools PROPERTIES IMPORTED_LOCATION "${SPIRV-Tools_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${SPIRV-Tools__INCLUDE_DIR}")
endif()
if (SPIRV-Tools-opt_LIBRARY)
add_library(glslang::SPIRV-Tools-opt UNKNOWN IMPORTED)
set_target_properties(glslang::SPIRV-Tools-opt PROPERTIES IMPORTED_LOCATION "${SPIRV-Tools-opt_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${SPIRV-Tools-opt_INCLUDE_DIR}")
endif()
add_library(glslang::OGLCompiler UNKNOWN IMPORTED)
set_target_properties(glslang::OGLCompiler PROPERTIES IMPORTED_LOCATION "${OGLCompiler_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${glslang_INCLUDE_DIRS}")
add_library(glslang::HLSL UNKNOWN IMPORTED)
set_target_properties(glslang::HLSL PROPERTIES IMPORTED_LOCATION "${HLSL_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${glslang_INCLUDE_DIRS}")
if(WIN32)
set_target_properties(glslang::glslang PROPERTIES IMPORTED_LOCATION_DEBUG "${glslang_LIBRARY_debug}")
set_target_properties(glslang::OSDependent PROPERTIES IMPORTED_LOCATION_DEBUG "${OSDependent_LIBRARY_debug}")
set_target_properties(glslang::SPIRV PROPERTIES IMPORTED_LOCATION_DEBUG "${SPIRV_LIBRARY_debug}")
if (SPIRV-Tools_LIBRARY_debug)
set_target_properties(glslang::SPIRV-Tools PROPERTIES IMPORTED_LOCATION_DEBUG "${SPIRV-Tools_LIBRARY_debug}")
endif()
if (SPIRV-Tools-opt_LIBRARY_debug)
set_target_properties(glslang::SPIRV-Tools-opt PROPERTIES IMPORTED_LOCATION_DEBUG "${SPIRV-Tools-opt_LIBRARY_debug}")
endif()
set_target_properties(glslang::OGLCompiler PROPERTIES IMPORTED_LOCATION_DEBUG "${OGLCompiler_LIBRARY_debug}")
set_target_properties(glslang::HLSL PROPERTIES IMPORTED_LOCATION_DEBUG "${HLSL_LIBRARY_debug}")
endif()
set(GLSLANG
glslang::glslang
glslang::OSDependent
glslang::SPIRV
glslang::OGLCompiler
glslang::HLSL
)
if (SPIRV-Tools-opt_LIBRARY)
list(APPEND GLSLANG glslang::SPIRV-Tools-opt)
endif()
if (SPIRV-Tools_LIBRARY)
list(APPEND GLSLANG glslang::SPIRV-Tools)
endif()
endif()

12
engine/CMakeLists.txt Executable file
View file

@ -0,0 +1,12 @@
add_subdirectory(core)
add_subdirectory(renderer)
add_subdirectory(utility)
add_subdirectory(gfx)
add_subdirectory(math)
add_subdirectory(log)
add_subdirectory(asset)
if(NOT ENABLE_IOS AND NOT ENABLE_TVOS)
add_subdirectory(audio)
add_subdirectory(tests)
endif()

View file

@ -0,0 +1,18 @@
set(SRC
include/asset_types.hpp
include/asset.hpp
include/assetptr.hpp
include/material_nodes.hpp
src/asset.cpp)
add_library(Asset ${SRC})
target_include_directories(Asset PUBLIC include)
target_link_libraries(Asset
PUBLIC
Math
PRIVATE
stb
Log
Core)
set_engine_properties(Asset)

View file

@ -0,0 +1,155 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <array>
#include "file.hpp"
#include "assetptr.hpp"
#include "asset_types.hpp"
#include "string_utils.hpp"
namespace std {
template <>
struct hash<file::Path> {
std::size_t operator()(const file::Path& k) const {
return hash<std::string>()(k);
}
};
}
template<typename T>
std::unique_ptr<T> load_asset(const file::Path p);
template<typename T>
bool can_load_asset(const file::Path p);
template<class AssetType>
using AssetStore = std::unordered_map<file::Path, std::unique_ptr<AssetType>>;
template<class... Assets>
class AssetPool : public AssetStore<Assets>... {
public:
template<typename T>
AssetPtr<T> add() {
const auto p = file::Path();
auto reference_block = get_reference_block(p);
AssetStore<T>::try_emplace(p, std::make_unique<T>());
return AssetPtr<T>(AssetStore<T>::at(p).get(), reference_block);
}
template<typename T>
AssetPtr<T> get(const file::Path path) {
return fetch<T>(path, get_reference_block(path));
}
template<typename T>
std::vector<T*> get_all() {
std::vector<T*> assets;
for(auto iter = AssetStore<T>::begin(); iter != AssetStore<T>::end(); iter++) {
auto& [p, asset] = *iter;
assets.push_back(asset.get());
}
return assets;
}
template<typename T>
AssetPtr<T> fetch(const file::Path path, ReferenceBlock* reference_block) {
if(!AssetStore<T>::count(path))
AssetStore<T>::try_emplace(path, load_asset<T>(path));
return AssetPtr<T>(AssetStore<T>::at(path).get(), reference_block);
}
std::tuple<Asset*, ReferenceBlock*> load_asset_generic(const file::Path path) {
Asset* asset = nullptr;
ReferenceBlock* block = nullptr;
(load_asset_generic<Assets>(path, asset, block), ...);
return {asset, block};
}
void perform_cleanup() {
for(auto iter = reference_blocks.begin(); iter != reference_blocks.end();) {
auto& [path, block] = *iter;
if(block->references == 0) {
((delete_asset<Assets>(path)), ...);
iter = reference_blocks.erase(iter);
} else {
iter = std::next(iter);
}
}
}
std::unordered_map<file::Path, std::unique_ptr<ReferenceBlock>> reference_blocks;
private:
ReferenceBlock* get_reference_block(const file::Path path) {
if(!reference_blocks.count(path))
reference_blocks.try_emplace(path, std::make_unique<ReferenceBlock>());
return reference_blocks[path].get();
}
template<typename T>
void load_asset_generic(const file::Path path, Asset*& at, ReferenceBlock*& block) {
if(can_load_asset<T>(path)) {
if(!AssetStore<T>::count(path))
AssetStore<T>::try_emplace(path, load_asset<T>(path));
at = AssetStore<T>::at(path).get();
block = get_reference_block(path);
}
}
template<typename T>
void delete_asset(const file::Path path) {
auto iter = AssetStore<T>::find(path);
if(iter != AssetStore<T>::end()) {
auto& [_, asset] = *iter;
asset.reset();
AssetStore<T>::erase(iter);
}
}
};
using AssetManager = AssetPool<Mesh, Material, Texture>;
inline std::unique_ptr<AssetManager> assetm;
std::unique_ptr<Mesh> load_mesh(const file::Path path);
std::unique_ptr<Material> load_material(const file::Path path);
std::unique_ptr<Texture> load_texture(const file::Path path);
void save_material(Material* material, const std::string_view path);
template<typename T>
std::unique_ptr<T> load_asset(const file::Path path) {
if constexpr (std::is_same_v<T, Mesh>) {
return load_mesh(path);
} else if constexpr(std::is_same_v<T, Material>) {
return load_material(path);
} else if constexpr(std::is_same_v<T, Texture>) {
return load_texture(path);
}
}
template<typename T>
bool can_load_asset(const file::Path path) {
if constexpr(std::is_same_v<T, Mesh>) {
return path.extension() == ".model";
} else if constexpr(std::is_same_v<T, Material>) {
return path.extension() == ".material";
} else if constexpr(std::is_same_v<T, Texture>) {
return path.extension() == ".png";
}
}

View file

@ -0,0 +1,84 @@
#pragma once
#include <map>
#include "assetptr.hpp"
#include "math.hpp"
#include "material_nodes.hpp"
#include "aabb.hpp"
class GFXBuffer;
class GFXTexture;
class Texture : public Asset {
public:
GFXTexture* handle = nullptr;
int width = 0, height = 0;
};
class GFXPipeline;
class Material : public Asset {
public:
std::vector<std::unique_ptr<MaterialNode>> nodes;
GFXPipeline* static_pipeline = nullptr;
GFXPipeline* skinned_pipeline = nullptr;
GFXPipeline* capture_pipeline = nullptr;
std::map<int, AssetPtr<Texture>> bound_textures;
};
constexpr int max_weights_per_vertex = 4;
struct BoneVertexData {
std::array<int, max_weights_per_vertex> ids;
std::array<float, max_weights_per_vertex> weights;
};
struct Bone {
int index = 0;
std::string name;
Matrix4x4 local_transform, final_transform;
Bone* parent = nullptr;
Vector3 position;
Quaternion rotation;
Vector3 scale;
};
class Mesh : public Asset {
public:
// meshes are rendered in parts if we cannot batch it in one call, i.e. a mesh
// with multiple materials with different textures, etc
struct Part {
std::string name;
AABB aabb;
GFXBuffer* bone_batrix_buffer = nullptr;
std::vector<Matrix4x4> offset_matrices;
uint32_t index_offset = 0, vertex_offset = 0, index_count = 0;
int32_t material_override = -1;
};
std::vector<Part> parts;
std::vector<Bone> bones;
Bone* root_bone = nullptr;
// atributes
GFXBuffer* position_buffer = nullptr;
GFXBuffer* normal_buffer = nullptr;
GFXBuffer* texture_coord_buffer = nullptr;
GFXBuffer* tangent_buffer = nullptr;
GFXBuffer* bitangent_buffer = nullptr;
GFXBuffer* bone_buffer = nullptr;
GFXBuffer* index_buffer = nullptr;
Matrix4x4 global_inverse_transformation;
uint32_t num_indices = 0;
};

View file

@ -0,0 +1,79 @@
#pragma once
#include <memory>
#include <unordered_map>
#include <array>
#include "file.hpp"
struct ReferenceBlock {
uint64_t references = 0;
};
class Asset {
public:
std::string path;
};
template<class T>
struct AssetPtr {
AssetPtr() {}
AssetPtr(T* ptr, ReferenceBlock* block) : handle(ptr), block(block) {
block->references++;
}
AssetPtr(const AssetPtr &rhs) {
handle = rhs.handle;
block = rhs.block;
if(block != nullptr)
block->references++;
}
AssetPtr& operator=(const AssetPtr& rhs) {
handle = rhs.handle;
block = rhs.block;
if(block != nullptr)
block->references++;
return *this;
}
~AssetPtr() {
if(block != nullptr)
block->references--;
}
void clear() {
if(block != nullptr)
block->references--;
block = nullptr;
handle = nullptr;
}
T* handle = nullptr;
ReferenceBlock* block = nullptr;
operator bool() const{
return handle != nullptr;
}
T* operator->() {
return handle;
}
T* operator->() const {
return handle;
}
T* operator*() {
return handle;
}
T* operator*() const {
return handle;
}
};

View file

@ -0,0 +1,241 @@
#pragma once
#include "renderer.hpp"
class Texture;
class MaterialNode;
enum class DataType {
Vector3,
Float,
AssetTexture
};
struct MaterialConnector {
MaterialConnector(std::string n, DataType t) : name(n), type(t) {}
MaterialConnector(std::string n, DataType t, bool isn) : name(n), type(t), is_normal_map(isn) {}
std::string name;
DataType type;
bool is_normal_map = false;
MaterialConnector* connected_connector = nullptr;
MaterialNode* connected_node = nullptr;
int connected_index = -1;
};
inline bool operator==(const MaterialConnector& a, const MaterialConnector& b) {
return a.name == b.name;
}
struct MaterialProperty {
std::string name;
DataType type;
MaterialProperty(std::string n, DataType t) : name(n), type(t) {}
Vector3 value;
AssetPtr<Texture> value_tex;
float float_value;
};
static int last_id = 0;
class MaterialNode {
public:
virtual ~MaterialNode() {}
int id = last_id++;
std::vector<MaterialConnector> inputs, outputs;
std::vector<MaterialProperty> properties;
virtual const char* get_prefix() = 0;
virtual const char* get_name() = 0;
virtual std::string get_glsl() = 0;
std::string get_connector_variable_name(MaterialConnector& connector) {
return std::string(get_prefix()) + std::to_string(id) + "_" + connector.name;
}
std::string get_property_variable_name(MaterialProperty& property) {
return std::string(get_prefix()) + std::to_string(id) + "_" + property.name;
}
std::string get_connector_value(MaterialConnector& connector) {
if(connector.connected_node != nullptr) {
if(connector.type != connector.connected_connector->type) {
return connector.connected_node->get_connector_variable_name(*connector.connected_connector) + ".r";
} else {
if(connector.is_normal_map) {
if(render_options.enable_normal_mapping) {
return "in_tbn * (2.0 * " + connector.connected_node->get_connector_variable_name(*connector.connected_connector) + " - 1.0)";
} else {
return "in_normal";
}
} else {
return connector.connected_node->get_connector_variable_name(*connector.connected_connector);
}
}
} else {
switch(connector.type) {
case DataType::Float:
return "0.0";
default:
return "vec3(1, 1, 1)";
}
}
}
std::string get_property_value(MaterialProperty& property) {
switch(property.type) {
case DataType::Vector3:
{
return "vec3(" + std::to_string(property.value.x) + ", " + std::to_string(property.value.y) + ", " + std::to_string(property.value.z) + ")";
}
break;
case DataType::Float:
{
return std::to_string(property.float_value);
}
break;
case DataType::AssetTexture:
{
return "texture(" + get_property_variable_name(property) + ", in_uv).rgb";
}
break;
}
}
MaterialConnector* find_connector(std::string name) {
for(auto& input : inputs) {
if(input.name == name)
return &input;
}
for(auto& output : outputs) {
if(output.name == name)
return &output;
}
return nullptr;
}
float x = 0.0f, y = 0.0f;
float width = 150.0f, height = 75.0f;
};
class MaterialOutput : public MaterialNode {
public:
MaterialOutput() {
inputs = {
MaterialConnector("Color", DataType::Vector3),
MaterialConnector("Roughness", DataType::Float),
MaterialConnector("Metallic", DataType::Float),
MaterialConnector("Normals", DataType::Vector3, true),
};
}
const char* get_prefix() override {
return "material_output_";
}
const char* get_name() override {
return "Material Output";
}
std::string get_glsl() override {
std::string glsl = "vec3 final_diffuse_color = Color;\n \
float final_roughness = Roughness;\n \
float final_metallic = Metallic;\n";
if(find_connector("Normals")->connected_node != nullptr) {
glsl += "vec3 final_normal = Normals;\n";
} else {
glsl += "vec3 final_normal = in_normal;\n";
}
return glsl;
}
};
class Vector3Constant : public MaterialNode {
public:
Vector3Constant() {
outputs = {MaterialConnector("Value", DataType::Vector3)};
properties = {MaterialProperty("Color", DataType::Vector3)};
}
const char* get_prefix() override {
return "vec3const_";
}
const char* get_name() override {
return "Vector3 Constant";
}
std::string get_glsl() override {
return "vec3 Value = Color;\n";
}
};
class FloatConstant : public MaterialNode {
public:
FloatConstant() {
outputs = {MaterialConnector("Value", DataType::Float)};
properties = {MaterialProperty("Data", DataType::Float)};
}
const char* get_prefix() override {
return "floatconst_";
}
const char* get_name() override {
return "Float Constant";
}
std::string get_glsl() override {
return "float Value = Data;\n";
}
};
class TextureNode : public MaterialNode {
public:
TextureNode() {
outputs = {MaterialConnector("Value", DataType::Vector3)};
properties = {MaterialProperty("Texture", DataType::AssetTexture)};
}
const char* get_prefix() override {
return "texture_";
}
const char* get_name() override {
return "Texture";
}
std::string get_glsl() override {
return "vec3 Value = Texture;\n";
}
};
class Geometry : public MaterialNode {
public:
Geometry() {
outputs = {MaterialConnector("Normal", DataType::Vector3)};
}
const char* get_prefix() override {
return "geometry_";
}
const char* get_name() override {
return "Geometry";
}
std::string get_glsl() override {
return "vec3 Normal = normalize(in_normal);\n";
}
};

387
engine/asset/src/asset.cpp Normal file
View file

@ -0,0 +1,387 @@
#include "asset.hpp"
#include <map>
#include <array>
#include <stb_image.h>
#include "log.hpp"
#include "engine.hpp"
#include "gfx.hpp"
#include "json_conversions.hpp"
#include "gfx_commandbuffer.hpp"
#include "assertions.hpp"
std::unique_ptr<Mesh> load_mesh(const file::Path path) {
Expects(!path.empty());
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Renderer, "Failed to load mesh from {}!", path);
return nullptr;
}
int version = 0;
file->read(&version);
if(version == 5 || version == 6) {
} else {
console::error(System::Renderer, "{} failed the mesh version check! reported version = {}", path, std::to_string(version));
return nullptr;
}
auto mesh = std::make_unique<Mesh>();
mesh->path = path;
enum MeshType : int {
Static,
Skinned
} mesh_type;
file->read(&mesh_type);
// TODO: use unsigned int here
int numVertices = 0;
file->read(&numVertices);
Expects(numVertices > 0);
const auto read_buffer = [&f = file.value(), numVertices](unsigned int size) -> GFXBuffer* {
auto buffer = engine->get_gfx()->create_buffer(nullptr, size * static_cast<unsigned int>(numVertices), false, GFXBufferUsage::Vertex);
auto buffer_ptr = reinterpret_cast<unsigned char*>(engine->get_gfx()->get_buffer_contents(buffer));
f.read(buffer_ptr, size * static_cast<unsigned int>(numVertices));
engine->get_gfx()->release_buffer_contents(buffer, buffer_ptr);
return buffer;
};
// read positions
mesh->position_buffer = read_buffer(sizeof(Vector3));
mesh->normal_buffer = read_buffer(sizeof(Vector3));
mesh->texture_coord_buffer = read_buffer(sizeof(Vector2));
mesh->tangent_buffer = read_buffer(sizeof(Vector3));
mesh->bitangent_buffer = read_buffer(sizeof(Vector3));
if(mesh_type == MeshType::Skinned)
mesh->bone_buffer = read_buffer(sizeof(BoneVertexData));
int numIndices = 0;
file->read(&numIndices);
Expects(numIndices > 0);
mesh->index_buffer = engine->get_gfx()->create_buffer(nullptr, sizeof(uint32_t) * numIndices, false, GFXBufferUsage::Index);
auto index_ptr = reinterpret_cast<uint32_t*>(engine->get_gfx()->get_buffer_contents(mesh->index_buffer));
file->read(index_ptr, sizeof(uint32_t) * numIndices);
engine->get_gfx()->release_buffer_contents(mesh->index_buffer, index_ptr);
int bone_len = 0;
file->read(&bone_len);
mesh->bones.reserve(bone_len);
if(bone_len > 0) {
file->read(&mesh->global_inverse_transformation);
std::map<std::string, uint32_t> boneMapping;
std::map<int, std::string> parentQueue;
for (int v = 0; v < bone_len; v++) {
std::string bone, parent;
file->read_string(bone);
file->read_string(parent);
Vector3 pos;
file->read(&pos);
Quaternion rot;
file->read(&rot);
Vector3 scl;
file->read(&scl);
int finalBoneIndex = 0;
if(boneMapping.count(bone)) {
finalBoneIndex = boneMapping[bone];
} else {
Bone b;
b.index = mesh->bones.size();
b.name = bone;
b.name = bone;
b.position = pos;
b.rotation = rot;
b.scale = scl;
if(parent != "none" && !parent.empty())
parentQueue[b.index] = parent;
mesh->bones.push_back(b);
boneMapping[bone] = b.index;
finalBoneIndex = b.index;
}
}
for(auto& [index, parentName] : parentQueue) {
for(auto& bone : mesh->bones) {
if(bone.name == parentName)
mesh->bones[index].parent = &bone;
}
}
for(auto& bone : mesh->bones) {
if(bone.parent == nullptr)
mesh->root_bone = &bone;
}
}
int numMeshes = 0;
file->read(&numMeshes);
Expects(numMeshes > 0);
mesh->parts.resize(numMeshes);
uint32_t vertexOffset = 0, indexOffset = 0;
for(int i = 0; i < numMeshes; i++) {
auto& p = mesh->parts[i];
p.vertex_offset = vertexOffset;
p.index_offset = indexOffset;
file->read_string(p.name);
if(version == 6) {
file->read(&p.aabb);
}
int numVerts = 0;
file->read(&numVerts);
file->read(&p.index_count);
int numBones = 0;
file->read(&numBones);
p.bone_batrix_buffer = engine->get_gfx()->create_buffer(nullptr, sizeof(Matrix4x4) * 128, true, GFXBufferUsage::Storage);
if(numBones > 0) {
p.offset_matrices.resize(numBones);
file->read(p.offset_matrices.data(), sizeof(Matrix4x4) * numBones);
}
file->read(&p.material_override);
vertexOffset += numVerts;
indexOffset += p.index_count;
}
mesh->num_indices = static_cast<uint32_t>(numIndices);
return mesh;
}
std::unique_ptr<Texture> load_texture(const file::Path path) {
Expects(!path.empty());
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Renderer, "Failed to load texture from {}!", path);
return nullptr;
}
bool should_generate_mipmaps = true;
auto texture_info_path = path;
texture_info_path.replace_extension(".json");
auto json_file = file::open(texture_info_path);
if(json_file != std::nullopt) {
nlohmann::json j;
json_file->read_as_stream() >> j;
should_generate_mipmaps = j["generate_mipmaps"];
}
file->read_all();
int width, height, channels;
unsigned char* data = stbi_load_from_memory(file->cast_data<unsigned char>(), file->size(), &width, &height, &channels, 4);
if(!data) {
console::error(System::Renderer, "Failed to load texture from {}!", path);
return nullptr;
}
Expects(width > 0);
Expects(height > 0);
auto texture = std::make_unique<Texture>();
texture->path = path;
texture->width = width;
texture->height = height;
GFXTextureCreateInfo createInfo = {};
createInfo.width = width;
createInfo.height = height;
createInfo.format = GFXPixelFormat::R8G8B8A8_UNORM;
createInfo.usage = GFXTextureUsage::Sampled;
if(should_generate_mipmaps)
createInfo.mip_count = std::floor(std::log2(std::max(width, height))) + 1;
texture->handle = engine->get_gfx()->create_texture(createInfo);
engine->get_gfx()->copy_texture(texture->handle, data, width * height * 4);
if(createInfo.mip_count > 1) {
GFXCommandBuffer* cmd_buf = engine->get_gfx()->acquire_command_buffer();
cmd_buf->generate_mipmaps(texture->handle, createInfo.mip_count);
engine->get_gfx()->submit(cmd_buf);
}
stbi_image_free(data);
return texture;
}
std::unique_ptr<Material> load_material(const file::Path path) {
Expects(!path.empty());
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Core, "Failed to load material from {}!", path);
return {};
}
nlohmann::json j;
file->read_as_stream() >> j;
auto mat = std::make_unique<Material>();
mat->path = path;
if(!j.count("version") || j["version"] != 2) {
console::error(System::Core, "Material {} failed the version check!", path);
return mat;
}
for(auto node : j["nodes"]) {
std::unique_ptr<MaterialNode> n;
auto name = node["name"];
if(name == "Material Output") {
n = std::make_unique<MaterialOutput>();
} else if(name == "Texture") {
n = std::make_unique<TextureNode>();
} else if(name == "Float Constant") {
n = std::make_unique<FloatConstant>();
}
n->id = node["id"];
n->x = node["x"];
n->y = node["y"];
n->width = node["width"];
n->height = node["height"];
for(auto& property : node["properties"]) {
for(auto& p : n->properties) {
if(property["name"] == p.name) {
p.value = property["value"];
p.float_value = property["float_value"];
if(!property["asset_value"].get<std::string>().empty()) {
p.value_tex = assetm->get<Texture>(file::app_domain / property["asset_value"].get<std::string>());
}
}
}
}
mat->nodes.emplace_back(std::move(n));
}
for(auto node : j["nodes"]) {
MaterialNode* n = nullptr;
for(auto& nn : mat->nodes) {
if(nn->id == node["id"])
n = nn.get();
}
if(n == nullptr)
continue;
for(auto connection : node["connections"]) {
for(auto [i, output] : utility::enumerate(n->outputs)) {
if(connection["name"] == output.name) {
for(auto& nn : mat->nodes) {
if(nn->id == connection["connected_node"]) {
output.connected_node = nn.get();
auto connector = nn->find_connector(connection["connected_connector"]);
output.connected_connector = connector;
connector->connected_index = i;
connector->connected_node = n;
connector->connected_connector = &output;
}
}
output.connected_index = connection["connected_index"];
}
}
}
}
return mat;
}
void save_material(Material* material, const std::string_view path) {
Expects(material != nullptr);
Expects(!path.empty());
nlohmann::json j;
j["version"] = 2;
for(auto& node : material->nodes) {
nlohmann::json n;
n["name"] = node->get_name();
n["id"] = node->id;
n["x"] = node->x;
n["y"] = node->y;
n["width"] = node->width;
n["height"] = node->height;
for(auto property : node->properties) {
nlohmann::json p;
p["name"] = property.name;
p["value"] = property.value;
p["asset_value"] = property.value_tex ? property.value_tex->path : "";
p["float_value"] = property.float_value;
n["properties"].push_back(p);
}
for(auto output : node->outputs) {
if(output.connected_connector != nullptr) {
nlohmann::json c;
c["name"] = output.name;
c["connected_connector"] = output.connected_connector->name;
c["connected_node"] = output.connected_node->id;
c["connected_index"] = output.connected_index;
n["connections"].push_back(c);
}
}
j["nodes"].push_back(n);
}
std::ofstream out(path.data());
out << j;
}

14
engine/audio/CMakeLists.txt Executable file
View file

@ -0,0 +1,14 @@
set(SRC
include/audio.hpp
src/audio.cpp)
add_library(Audio STATIC ${SRC})
target_include_directories(Audio PUBLIC include)
target_link_libraries(Audio PRIVATE
Log
opus
opusfile
portaudio_static
Core)
target_compile_features(Audio PUBLIC cxx_std_17)
set_target_properties(Audio PROPERTIES CXX_EXTENSIONS OFF)

17
engine/audio/include/audio.hpp Executable file
View file

@ -0,0 +1,17 @@
#pragma once
#include <fstream>
#include <string>
#include <vector>
#include <string_view>
#include "file.hpp"
/*
* Audio API
*/
namespace audio {
void initialize();
void play_file(const file::Path path, const float gain = 1.0f);
}

92
engine/audio/src/audio.cpp Executable file
View file

@ -0,0 +1,92 @@
#include "audio.hpp"
#include <portaudio.h>
#include <opusfile.h>
#include <algorithm>
#include <cstring>
#include "log.hpp"
#include "file.hpp"
#include "platform.hpp"
constexpr int num_channels = 2;
constexpr int sample_rate = 48000;
constexpr int frame_size = 960;
constexpr int frames_per_buffer = 480;
struct AudioFile {
float gain = 1.0f;
bool finished = false;
OggOpusFile* handle = nullptr;
};
std::vector<AudioFile> audio_files;
static int pa_callback(const void*, void* buffer,
unsigned long,
const PaStreamCallbackTimeInfo*,
PaStreamCallbackFlags,
void*) {
auto out = static_cast<float*>(buffer);
float final_values[frame_size] = {0.0f};
for(auto& file : audio_files) {
if(file.finished)
continue;
float values[frame_size] = {0.0f};
int val = op_read_float_stereo(file.handle, values, frame_size);
if(val == 0)
file.finished = true;
for(int i = 0; i < frame_size; i++) {
final_values[i] += values[i] * file.gain;
}
}
memcpy(out, final_values, frame_size * sizeof(float));
audio_files.erase(std::remove_if(audio_files.begin(),
audio_files.end(),
[](AudioFile& x){return x.finished;}),
audio_files.end());
return 0;
}
void audio::initialize() {
platform::mute_output();
Pa_Initialize();
platform::unmute_output();
PaStream* stream;
Pa_OpenDefaultStream(&stream,
0,
num_channels,
paFloat32,
sample_rate,
frames_per_buffer,
pa_callback,
nullptr);
Pa_StartStream(stream);
}
void audio::play_file(const file::Path path, const float gain) {
auto audio_file = file::open(path);
if(audio_file == std::nullopt)
return;
audio_file->read_all();
AudioFile file;
file.gain = gain;
file.handle = op_open_memory(audio_file->cast_data<unsigned char>(), audio_file->size(), nullptr);
audio_files.push_back(file);
}

55
engine/core/CMakeLists.txt Executable file
View file

@ -0,0 +1,55 @@
if(TARGET BulletDynamics)
set(BULLET_LIBRARIES BulletDynamics BulletCollision LinearMath)
set(BULLET_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/_deps/bullet-src/src)
else()
find_package(Bullet REQUIRED)
endif()
set(SRC
include/engine.hpp
include/app.hpp
include/input.hpp
include/cutscene.hpp
include/physics.hpp
include/scene.hpp
include/imguilayer.hpp
include/uielement.hpp
include/screen.hpp
include/object.hpp
include/debug.hpp
include/components.hpp
include/platform.hpp
include/file.hpp
include/imgui_utility.hpp
include/frustum.hpp
src/file.cpp
src/engine.cpp
src/input.cpp
src/physics.cpp
src/imguilayer.cpp
src/screen.cpp
src/scene.cpp
src/debug.cpp)
if(NOT ENABLE_IOS AND NOT ENABLE_TVOS)
set(EXTRA_LIBRARIES Audio sol)
endif()
add_library(Core STATIC ${SRC})
target_include_directories(Core PUBLIC
include)
target_include_directories(Core SYSTEM PUBLIC
${BULLET_INCLUDE_DIRS})
target_link_libraries(Core PUBLIC
Utility
GFX
Renderer
nlohmann_json
magic_enum
${BULLET_LIBRARIES}
imgui
Log
${EXTRA_LIBRARIES}
Asset)
set_engine_properties(Core)

29
engine/core/include/app.hpp Executable file
View file

@ -0,0 +1,29 @@
#pragma once
class Engine;
/// The base class for any Prism application.
class App {
public:
/// Called when a render context is available.
virtual void initialize_render() {}
/// Called when the engine is about to quit. Should be overriden if an app needs to save data before qutting.
virtual void prepare_quit() {}
/// Called to check whether or not an app should intervene quitting.
virtual bool should_quit() {
return true;
}
/// Called when a engine starts a new frame. Typically used for inserting new imgui windows.
virtual void begin_frame() {}
/** Called during the engine's update cycle.
@param delta_time Delta time in milliseconds.
*/
virtual void update([[maybe_unused]] const float delta_time) {}
};
/// This is an app's equivalent main(). You can check command line arguments through Engine::comand_line_arguments.
void app_main(Engine* engine);

View file

@ -0,0 +1,112 @@
#pragma once
#include "assetptr.hpp"
#include "screen.hpp"
class btCollisionShape;
class btRigidBody;
class Scene;
class Mesh;
class Material;
struct Collision {
enum class Type {
Cube,
Capsule
} type = Type::Cube;
Vector3 size;
bool is_trigger = false;
std::string trigger_id;
bool exclude_from_raycast = false;
bool trigger_entered = false;
btCollisionShape* shape = nullptr;
};
struct Rigidbody {
enum class Type {
Dynamic,
Kinematic
} type = Type::Dynamic;
int mass = 0;
float friction = 0.5f;
bool enable_deactivation = true;
bool enable_rotation = true;
void add_force(const Vector3 force) {
stored_force += force;
}
btRigidBody* body = nullptr;
Vector3 stored_force;
};
struct Data {
std::string name, prefab_path;
Object parent = NullObject;
bool editor_object = false;
};
struct Transform {
Vector3 position, scale = Vector3(1);
Quaternion rotation;
Matrix4x4 model;
Vector3 get_world_position() const {
return {
model[3][0],
model[3][1],
model[3][2]
};
}
};
struct Renderable {
AssetPtr<Mesh> mesh;
std::vector<AssetPtr<Material>> materials;
};
struct Light {
enum class Type : int{
Point = 0,
Spot = 1,
Sun = 2
} type = Type::Point;
Vector3 color = Vector3(1);
float power = 10.0f;
float size = 1.0f;
float spot_size = 40.0f;
bool enable_shadows = true;
bool use_dynamic_shadows = false;
};
struct Camera {
float fov = 75.0f;
float near = 0.1f;
float exposure = 1.0;
Matrix4x4 view, perspective;
};
struct UI {
int width = 1920, height = 1080;
std::string ui_path;
ui::Screen* screen = nullptr;
};
struct EnvironmentProbe {
bool is_sized = true;
Vector3 size = Vector3(10);
float intensity = 1.0;
};

View file

@ -0,0 +1,75 @@
#pragma once
#include <vector>
#include "vector.hpp"
#include "quaternion.hpp"
struct PositionKeyFrame {
float time;
Vector3 value;
};
inline bool operator==(const PositionKeyFrame& lhs, const PositionKeyFrame& rhs) {
return nearly_equal(lhs.time, rhs.time) && nearly_equal(lhs.value.x, rhs.value.x);
}
struct RotationKeyFrame {
float time;
Quaternion value;
};
struct ScaleKeyFrame {
float time;
Vector3 value;
};
struct AnimationChannel {
std::string id;
Object target = NullObject;
Bone* bone = nullptr;
std::vector<PositionKeyFrame> positions;
std::vector<RotationKeyFrame> rotations;
std::vector<ScaleKeyFrame> scales;
};
inline bool operator==(const AnimationChannel& lhs, const AnimationChannel& rhs) {
return lhs.id == rhs.id && lhs.target == rhs.target && lhs.positions == rhs.positions;
}
struct Animation {
double ticks_per_second = 0.0;
double duration = 0.0;
std::vector<AnimationChannel> channels;
};
struct Shot {
int begin, length;
std::vector<AnimationChannel> channels;
Scene* scene = nullptr;
};
inline bool operator==(const Shot& lhs, const Shot& rhs) {
return lhs.begin == rhs.begin && lhs.length == rhs.length;
}
class Cutscene {
public:
std::vector<Shot> shots;
int get_real_end() {
int end = -1;
for(auto& shot : shots) {
if((shot.begin + shot.length) >= end)
end = shot.begin + shot.length;
}
return end;
}
};

View file

@ -0,0 +1,3 @@
#pragma once
void draw_debug_ui();

399
engine/core/include/engine.hpp Executable file
View file

@ -0,0 +1,399 @@
#pragma once
#include <vector>
#include <nlohmann/json.hpp>
#include <iosfwd>
#include <map>
#include <memory>
#include <string>
#include <string_view>
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
#include <sol.hpp>
#endif
#include "scene.hpp"
#include "renderer.hpp"
#include "input.hpp"
#include "cutscene.hpp"
#include "physics.hpp"
#include "file.hpp"
#include "object.hpp"
#include "imguilayer.hpp"
#include "shadowpass.hpp"
#include "scenecapture.hpp"
#include "smaapass.hpp"
#include "gaussianhelper.hpp"
#include "common.hpp"
class GFX;
namespace ui {
class Screen;
}
class App;
struct Timer;
struct AnimationTarget {
float current_time = 0.0f;
float animation_speed_modifier = 1.0f;
bool looping = false;
Object target;
Animation animation;
};
/// An object that contains glue between App and systems such as the Renderer.
class Engine {
public:
/**
Constructs an Engine with command line arguments. Can be accessed later from command_line_arguments.
@param argc Numer of arguments. Can be null.
@param argv Array of strings containing arguments. Can be null.
*/
Engine(const int argc, char* argv[]);
/// Command line arguments, can be empty.
std::vector<std::string_view> command_line_arguments;
/** Sets the app object.
@param app The app object to set. Must not be null.
*/
void set_app(App* app);
/** Gets the current app object
@return The current app object. Can be null.
*/
App* get_app() const;
/** Call to begin the next frame.
@param delta_time Delta time in seconds.
*/
void begin_frame(const float delta_time);
/** Call to start updating the current frame.
@param delta_time Delta time in seconds.
*/
void update(const float delta_time);
/** Call to begin rendering for a window.
@param index The index of the window to begin rendering for.
*/
void render(const int index);
/// Pause updating.
void pause();
/// Resume updating.
void unpause();
/** Query the current pause state.
@return Whether or not the engine is currently paused.
*/
bool is_paused() const;
/// Request to begin quitting immediately. This is not forced, and can be canceled by the user, platform, or app.
void quit();
/// Call right before the platform is about to quit the application, and requests the app or other systems to save work.
void prepare_quit();
/// Query whether or not the engine is in the process of quitting.
bool is_quitting() const;
/** Set the GFX api to use.
@param gfx The GFX object to use. Must not be null.
*/
void set_gfx(GFX* gfx);
/** Get the current GFX api.
@return The current GFX api. Can be null.
*/
GFX* get_gfx();
/** Get the input system.
@return Instance of the input system. Will not be null.
*/
Input* get_input();
/** Get the renderer for a window.
@param index Index of the window. Default is 0.
@return Instance of the renderer. Will not be null.
*/
Renderer* get_renderer(const int index = 0);
/** Get the physics system.
@return Instance of the physics system. Will not be null.
*/
Physics* get_physics();
/// Creates an empty scene with no path. This will change the current scene.
void create_empty_scene();
/** Load a scene from disk. This will change the current scene if successful.
@param path The scene file path.
@return Returns a instance of the scene is successful, and nullptr on failure.
*/
Scene* load_scene(const file::Path path);
/** Save the current scene to disk.
@param path The absolute file path.
*/
void save_scene(const std::string_view path);
/** Load a UI screen from disk. This will not change the current screen.
@param path The screen file path.
@return Returns a instance of the screen if successful, and nullptr on failure.
*/
ui::Screen* load_screen(const file::Path path);
/** Set the current screen.
@param screen The screen object to set as current. Can be null.
*/
void set_screen(ui::Screen* screen);
/** Gets the current screen.
@return The current screen. Can be null.
*/
ui::Screen* get_screen() const;
/** Load a prefab from disk.
@param scene The scene to add the prefab to.
@param path The prefab file path.
@param override_name If not empty, the root object's new name. Defaulted to a empty string.
*/
Object add_prefab(Scene& scene, const file::Path path, const std::string_view override_name = "");
/** Save a tree of objects as a prefab to disk.
@param root The parent object to save as a prefab.
@param path The absolue file path.
*/
void save_prefab(const Object root, const std::string_view path);
/** Deserializes a animation channel from JSON.
@param j The animation channel json to deserialize.
@return An animation channel.
*/
AnimationChannel load_animation(nlohmann::json j);
/** Load an animation from disk.
@param path The animation file path.
@return An animation.
*/
Animation load_animation(const file::Path path);
/** Load a cutscene from disk. This changes the current cutscene.
@param path The cutscene file path.
*/
void load_cutscene(const file::Path path);
/** Saves the current cutscene to disk.
@param path The absolute file path.
*/
void save_cutscene(const std::string_view path);
/** Adds the window for engine management.
@param native_handle The platform's native handle to it's window equivalent.
@param identifier The identifier of the new window.
@param extent The extent of the window.
*/
void add_window(void* native_handle, const int identifier, const Extent extent);
/** Removes the window from engine management. Should be called before the window is actually closed.
@param identifier The identifier of the window to remove.
*/
void remove_window(const int identifier);
/** Called when the window has changed size.
@param identifier The window that has been resized.
@param extent The new extent of the window.
*/
void resize(const int identifier, const Extent extent);
/** Called when a key has been pressed.
@param keyCode A platform-specific key code.
@note Use platform::get_keycode to get a InputButton equivalent if needed.
@note This function is only intended for debug purposes and all production code should be using the Input system instead.
*/
void process_key_down(const unsigned int keyCode);
/** Called when a key has been released.
@param keyCode A platform-specific key code.
@note Use platform::get_keycode to get a InputButton equivalent if needed.
@note This function is only intended for debug purposes and all production code should be using the Input system instead.
*/
void process_key_up(const unsigned int keyCode);
/** Called when a mouse button has been clicked..
@param button The mouse button.
@param offset The mouse position relative to the window where the click occured.
@note This function is only intended for debug purposes and all production code should be using the Input system instead.
*/
void process_mouse_down(const int button, const Offset offset);
/** Pushes a UI event for the current screen. Does nothing if there is no screen set.
@param name The name of the event.
@param data Data for the event. Defaulted to an empty string.
*/
void push_event(const std::string_view name, const std::string_view data = "");
/** Load a localization file from disk. This will change the current localization.
@param path The localization file path.
*/
void load_localization(const std::string_view path);
/** Queries whether or not the current localization loaded has a key.
@param id The key to query.
@return Whether or not the locale has the key specified.
@note Having no localization loaded will always return false.
*/
bool has_localization(const std::string_view id) const;
/** Localizes a string.
@param id The locale key to use.
@return A localized string if the key is found, or an empty string if not found.
@note Having no localization loaded will always return a empty string.
*/
std::string localize(const std::string id);
/** Adds a timer to the list of timers.
@param timer The timer to add.
@note The timer instance is passed by reference. Use this to keep track of your timers without having to query it's state back.
*/
void add_timer(Timer& timer);
/** Gets the current scene.
@return The current scene if set, or nullptr if there isn't one.
*/
Scene* get_scene();
/** Get a scene by path. This does not change the current scene.
@param name The path to the scene file.
@return Returns a scene if it was previously loaded and found, or nullptr on failure.
*/
Scene* get_scene(const std::string_view name);
/** Set the current scene.
@param scene The scene to set. Can be null.
*/
void set_current_scene(Scene* scene);
/** Get the current scene's path.
@return If a scene is loaded, the path to the scene. Can be an empty string if there is no scene loaded, or if it's an empty scene.
*/
std::string_view get_scene_path() const;
/** Updates the Transform hierarchy for a scene.
@param scene The scene to update.
*/
void update_scene(Scene& scene);
/// The current cutscene.
std::unique_ptr<Cutscene> cutscene;
/// The current time in seconds for cutscene playback.
float current_cutscene_time = 0.0f;
/// The cutscene playback state.
bool play_cutscene = false;
/** Start playback of an animation.
@param animation The animation to play.
@param object The animation's target.
@param looping Whether or not the animation should loop or be discarded when finished. Default is false.
*/
void play_animation(const Animation animation, const Object target, const bool looping = false);
/** Sets the animation speed of an object.
@param target The object you want to change the animation speed of.
@param modifier The speed to play the object's animations at. A modifier of 2.0 would be 2x the speed, and 0.5 would be 1/2x the speed.
*/
void set_animation_speed_modifier(const Object target, const float modifier);
/** Stops all animation for an object.
@param target The object you want the animations to stop for.
*/
void stop_animation(const Object target);
/// If there is a render context available. If this is false, avoid doing any GFX or Renderer work as it could crash or cause undefined behavior.
bool render_ready = false;
/// If physics should upate. This is a control indepentent of the pause state.
bool update_physics = true;
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
sol::state lua;
#endif
#if defined(PLATFORM_TVOS) || defined(PLATFORM_IOS)
bool debug_enabled = true;
#else
bool debug_enabled = false;
#endif
private:
void create_lua_interface();
void setup_scene(Scene& scene);
void on_remove(Object object);
bool _paused = false;
ui::Screen* _current_screen = nullptr;
Scene* _current_scene = nullptr;
std::vector<std::unique_ptr<Scene>> _scenes;
std::map<std::string, Scene*> _path_to_scene;
struct Window {
int identifier = -1;
Extent extent;
bool quitRequested = false;
std::unique_ptr<Renderer> renderer = nullptr;
};
std::vector<Window> _windows;
Window* get_window(const int identifier) {
for(auto& window : _windows) {
if(window.identifier == identifier)
return &window;
}
return nullptr;
}
void calculate_bone(Mesh& mesh, const Mesh::Part& part, Bone& bone, const Bone* parent_bone = nullptr);
void calculate_object(Scene& scene, Object object, const Object parent_object = NullObject);
Shot* get_shot(const float time);
void update_animation(const Animation& anim, const float time);
void update_cutscene(const float time);
void update_animation_channel(Scene& scene, const AnimationChannel& channel, const float time);
App* _app = nullptr;
GFX* _gfx = nullptr;
std::unique_ptr<Input> _input = nullptr;
std::unique_ptr<Physics> _physics = nullptr;
std::vector<Timer*> _timers, _timersToRemove;
std::map<std::string, std::string> _strings;
std::vector<AnimationTarget> _animation_targets;
std::unique_ptr<ImGuiLayer> _imgui = nullptr;
const InputButton debug_button = InputButton::Q;
};
inline bool operator==(const AnimationTarget& a1, const AnimationTarget& a2) {
return a1.current_time == a2.current_time;
}
inline Engine* engine = nullptr;

133
engine/core/include/file.hpp Executable file
View file

@ -0,0 +1,133 @@
#pragma once
#include <fstream>
#include <string>
#include <vector>
#include <optional>
#include <array>
#include <filesystem>
namespace file {
enum class Domain {
System,
Internal,
App
};
/// Represents a file handle. The file may or may not be fully loaded in memory.
class File {
public:
File(FILE* handle) : handle(handle) {}
File(File&& f) noexcept :
mem(std::move(f.mem)),
handle(std::exchange(f.handle, nullptr)),
data(std::move(f.data)) {}
~File() {
fclose(handle);
}
/** Loads a type.
@param t The pointer to the type.
@param s If not 0, the size to load is derived from sizeof(T).
*/
template<typename T>
void read(T* t, const size_t s = 0) {
fread(t, s == 0 ? sizeof(T) : s, 1, handle);
}
/// Reads a string. Assumes the length is an unsigned integer.
void read_string(std::string& str) {
unsigned int length = 0;
read(&length);
if(length > 0) {
str.resize(length);
read(str.data(), sizeof(char) * length);
}
}
/// Loads the entire file into memory, accessible via cast_data()
void read_all() {
fseek(handle, 0L, SEEK_END);
const size_t _size = static_cast<size_t>(ftell(handle));
rewind(handle);
data.resize(_size);
fread(data.data(), _size, 1, handle);
}
/// If the file is loaded in memory, cast the underlying data.
template<typename T>
T* cast_data() {
return reinterpret_cast<T*>(data.data());
}
/// If the file is loaded in memory, return the size of the file.
size_t size() const {
return data.size();
}
/// Reads the entire file as a string.
std::string read_as_string() {
auto s = read_as_stream();
return std::string((std::istreambuf_iterator<char>(s)),
std::istreambuf_iterator<char>());
}
/// Reads the entire file as a ifstream, useful for JSON deserialization.
std::istream read_as_stream() {
read_all();
auto char_data = cast_data<char>();
mem = std::make_unique<membuf>(char_data, char_data + size());
return std::istream(mem.get());
}
private:
struct membuf : std::streambuf {
inline membuf(char* begin, char* end) {
this->setg(begin, begin, end);
}
};
std::unique_ptr<membuf> mem;
FILE* handle = nullptr;
std::vector<std::byte> data;
};
using Path = std::filesystem::path;
/** Sets the domain path to a location in the filesystem.
@param domain The domain type.
@param mode The access mode.
@param path The absolute file path.
*/
void set_domain_path(const Domain domain, const Path path);
Path get_domain_path(const Domain domain);
/// Converts an absolute path to a domain relative path.
Path get_relative_path(const Domain domain, const Path path);
/// Returns the path to a writeable directory.
Path get_writeable_directory();
/**
Opens a file handle.
@param domain The file domain.
@param path The file path.
@param binary_mode Whether or not to open the file as binary or ASCII. Defaults to false.
@return An optional with a value if the file was loaded correctly, otherwise it's empty.
*/
std::optional<File> open(const Path path, const bool binary_mode = false);
Path root_path(const Path path);
inline Path internal_domain = "/internal", app_domain = "/app";
}
inline std::array<std::string, 3> domain_data;

99
engine/core/include/frustum.hpp Executable file
View file

@ -0,0 +1,99 @@
#pragma once
struct CameraFrustum {
std::array<Plane, 6> planes;
};
inline CameraFrustum extract_frustum(const Matrix4x4 combined) {
CameraFrustum frustum;
// left plane
frustum.planes[0].a = combined.data[0][3] + combined.data[0][0];
frustum.planes[0].b = combined.data[1][3] + combined.data[1][0];
frustum.planes[0].c = combined.data[2][3] + combined.data[2][0];
frustum.planes[0].d = combined.data[3][3] + combined.data[3][0];
// right plane
frustum.planes[1].a = combined.data[0][3] - combined.data[0][0];
frustum.planes[1].b = combined.data[1][3] - combined.data[1][0];
frustum.planes[1].c = combined.data[2][3] - combined.data[2][0];
frustum.planes[1].d = combined.data[3][3] - combined.data[3][0];
// top plane
frustum.planes[2].a = combined.data[0][3] - combined.data[0][1];
frustum.planes[2].b = combined.data[1][3] - combined.data[1][1];
frustum.planes[2].c = combined.data[2][3] - combined.data[2][1];
frustum.planes[2].d = combined.data[3][3] - combined.data[3][1];
// bottom plane
frustum.planes[3].a = combined.data[0][3] + combined.data[0][1];
frustum.planes[3].b = combined.data[1][3] + combined.data[1][1];
frustum.planes[3].c = combined.data[2][3] + combined.data[2][1];
frustum.planes[3].d = combined.data[3][3] + combined.data[3][1];
// near plane
frustum.planes[4].a = combined.data[0][2];
frustum.planes[4].b = combined.data[1][2];
frustum.planes[4].c = combined.data[2][2];
frustum.planes[4].d = combined.data[3][2];
// far plane
frustum.planes[5].a = combined.data[0][3] - combined.data[0][2];
frustum.planes[5].b = combined.data[1][3] - combined.data[1][2];
frustum.planes[5].c = combined.data[2][3] - combined.data[2][2];
frustum.planes[5].d = combined.data[3][3] - combined.data[3][2];
return frustum;
}
inline CameraFrustum camera_extract_frustum(Scene& scene, Object cam) {
const auto camera_component = scene.get<Camera>(cam);
const Matrix4x4 combined = camera_component.perspective * camera_component.view;
return extract_frustum(combined);
}
inline CameraFrustum normalize_frustum(const CameraFrustum& frustum) {
CameraFrustum normalized_frustum;
for(int i = 0; i < 6; i++)
normalized_frustum.planes[i] = normalize(frustum.planes[i]);
return normalized_frustum;
}
inline bool test_point_plane(const Plane& plane, const Vector3& point) {
return distance_to_point(plane, point) > 0.0;
}
inline bool test_point_frustum(const CameraFrustum& frustum, const Vector3& point) {
bool inside_frustum = false;
for(int i = 0; i < 6; i++)
inside_frustum |= !test_point_plane(frustum.planes[i], point);
return !inside_frustum;
}
inline bool test_aabb_frustum(const CameraFrustum& frustum, const AABB& aabb) {
for(int i = 0; i < 6; i++) {
int out = 0;
for(const auto point : get_points(aabb))
out += (distance_to_point(frustum.planes[i], point) < 0.0) ? 1 : 0;
if(out == 8)
return false;
}
return true;
}
inline AABB get_aabb_for_part(const Transform& transform, const Mesh::Part& part) {
AABB aabb = {};
aabb.min = part.aabb.min - transform.get_world_position();
aabb.max = transform.get_world_position() + part.aabb.max;
aabb.min *= transform.scale;
aabb.max *= transform.scale;
return aabb;
}

View file

@ -0,0 +1,64 @@
#pragma once
#include <imgui.h>
#include "utility.hpp"
#include "math.hpp"
namespace ImGui {
template<typename T>
inline bool ComboEnum(const char* label, T* t) {
bool result = false;
const auto preview_value = utility::enum_to_string(*t);
if (ImGui::BeginCombo(label, preview_value.c_str())) {
for(auto& name : magic_enum::enum_values<T>()) {
const auto n = utility::enum_to_string(name);
if (ImGui::Selectable(n.c_str(), *t == name)) {
*t = name;
result = true;
}
}
ImGui::EndCombo();
}
return result;
}
template<typename T, typename TMax>
void ProgressBar(const char* label, const T value, const TMax max) {
const float progress_saturated = value / static_cast<float>(max);
char buf[32] = {};
sprintf(buf, "%d/%d", static_cast<int>(value), static_cast<int>(max));
ImGui::ProgressBar(progress_saturated, ImVec2(0.0f, 0.0f), buf);
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Text(label);
}
inline bool DragQuat(const char* label, Quaternion* quat) {
bool result = false;
Vector3 euler = quat_to_euler(*quat);
euler.x = degrees(euler.x);
euler.y = degrees(euler.y);
euler.z = degrees(euler.z);
if(ImGui::DragFloat3(label, euler.ptr(), 1.0f, 0.0f, 0.0f, "%.3f°")) {
euler.x = radians(euler.x);
euler.y = radians(euler.y);
euler.z = radians(euler.z);
*quat = euler_to_quat(euler);
result = true;
}
return result;
}
}

View file

@ -0,0 +1,12 @@
#pragma once
class ImGuiLayer {
public:
ImGuiLayer();
void begin_frame(const float delta_time);
void render(int index);
void process_key_down(unsigned int keyCode);
void process_key_up(unsigned int keyCode);
};

86
engine/core/include/input.hpp Executable file
View file

@ -0,0 +1,86 @@
#pragma once
#include <vector>
#include <map>
#include "platform.hpp"
enum class Axis {
MouseX,
MouseY,
ScrollX,
ScrollY,
LeftStickX,
LeftStickY,
RightStickX,
RightStickY
};
struct InputBindingData {
std::string name;
std::map<InputButton, float> buttons;
std::vector<Axis> axises;
float value = 0.0f;
bool repeat = false;
InputButton last_button = InputButton::Invalid;
};
/** Input system designed for handling events in a cross platform manner and across different input types.
*/
class Input {
public:
/// Updates input bindings and their values.
void update();
/** Add a new binding.
@param name The binding name.
*/
void add_binding(const std::string& name);
/** Ties a button-type input to the binding.
@param name The binding name.
@param button The button to associate with.
@param value The resulting input value when the button is pressed. Default is 1.0.
*/
void add_binding_button(const std::string& name, InputButton button, float value = 1.0f);
/** Ties an axis-type input to the button.
@param name The binding name.
@param button The axis to associate with.
*/
void add_binding_axis(const std::string& name, Axis axis);
/** Gets the input value of a binding.
@param name The binding name.
@return The input value associated with that binding, or 0.0 if none is found.
@note If this binding is tied to a button-type input, assume that 0.0 means not pressed.
@note If this binding is tied to an axis-type input, assume that 0.0 means the stick is centered.
*/
float get_value(const std::string& name);
/** Gets whether or not the binding is pressed.
@param name The binding name.
@param repeating If true, return a correct value regardless if it was identical for multiple frames. If false, any continued input past the first frame of being pressed is ignored and the input is considered released.
@return If the binding is found, return true if the binding value is 1.0. If no binding is found or if the binding has continued pressing and repeating is turned off, returns false.
*/
bool is_pressed(const std::string& name, bool repeating = false);
/** Queries if the binding is repeating it's input.
@param name The binding name.
@return If the binding is found, returns true if the binding's values have been identical for multiple frames. Returns false if the binding is not found.
*/
bool is_repeating(const std::string& name);
/// Returns all of the bindings registered with this Input system.
std::vector<InputBindingData> get_bindings() const;
private:
std::vector<InputBindingData> _input_bindings;
std::tuple<int, int> _last_cursor_position;
};

6
engine/core/include/object.hpp Executable file
View file

@ -0,0 +1,6 @@
#pragma once
#include <cstdint>
using Object = uint64_t;
constexpr Object NullObject = 0;

39
engine/core/include/physics.hpp Executable file
View file

@ -0,0 +1,39 @@
#pragma once
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Weverything"
#if defined(PLATFORM_MACOS) || defined(PLATFORM_IOS) || defined(PLATFORM_TVOS)
#include <btBulletDynamicsCommon.h>
#else
#include <bullet/btBulletDynamicsCommon.h>
#endif
#include <memory>
#pragma clang diagnostic pop
#include "vector.hpp"
#include "object.hpp"
class Physics {
public:
Physics();
void update(float deltaTime);
void reset();
void remove_object(Object obj);
struct RayResult {
bool hasHit;
Vector3 location;
};
RayResult raycast(Vector3 from, Vector3 to);
private:
std::unique_ptr<btBroadphaseInterface> broadphase;
std::unique_ptr<btDefaultCollisionConfiguration> collisionConfiguration;
std::unique_ptr<btCollisionDispatcher> dispatcher;
std::unique_ptr<btSequentialImpulseConstraintSolver> solver;
std::unique_ptr<btDiscreteDynamicsWorld> world;
};

173
engine/core/include/platform.hpp Executable file
View file

@ -0,0 +1,173 @@
#pragma once
#include <functional>
#include <string>
#include <string_view>
#include "common.hpp"
/// Requestable window flags, which may or may not be respected by the platform.
enum class WindowFlags {
None,
Resizable,
Borderless
};
/// Represents a button. This includes keyboard, gamepad and mouse buttons.
enum class InputButton {
Invalid,
C,
V,
X,
Y,
Z,
Backspace,
Enter,
W,
A,
S,
D,
Q,
Shift,
Alt,
Super,
Escape,
Tab,
Ctrl,
Space,
LeftArrow,
RightArrow,
// gamepad inputs
ButtonA,
ButtonB,
ButtonX,
ButtonY,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
// mouse inputs
MouseLeft,
MouseRight
};
enum class PlatformFeature {
Windowing
};
namespace platform {
/// Returns a human readable platform name, e.g. Linux.
const char* get_name();
/// Queries whether or not the platform supports a certain feature.
bool supports_feature(const PlatformFeature feature);
/** Opens a new window.
@param title The title of the window.
@param rect The requested size and position of the window.
@param flags The requested window flags.
@note Depending on the platform, some of these parameters might be unused. The best practice is to always assume that none of them may be used.
@note On platforms that do not support the Windowing feature, calling open_window more than once is not supported. In this case, the same identifier is returned.
@return A valid window identifier.
*/
int open_window(const std::string_view title, const Rectangle rect, const WindowFlags flags);
/** Closes a window.
@param index The window to close.
*/
void close_window(const int index);
/// Forces the platform to quit the application. This is not related to Engine::quit().
void force_quit();
/// Gets the content scale for the window. 1.0 would be 1x scale, 2.0 would be 2x scale, etc.
float get_window_dpi(const int index);
/// Gets the content scale for the monitor. 1.0 would be 1x scale, 2.0 would be 2x scale, etc.
float get_monitor_dpi();
/// Get the monitor resolution.
Rectangle get_monitor_resolution();
/// Get the monitor work area. For example on macOS this may exclude the areas of the menu bar and dock.
Rectangle get_monitor_work_area();
/// Get the window position.
Offset get_window_position(const int index);
/// Get the window size, note that in hidpi scenarios this is the non-scaled resolution.
Extent get_window_size(const int index);
/// Get the window's drawable size. Always use this instead of manually multiplying the window size by the content scale.
Extent get_window_drawable_size(const int index);
/// Query whether or not the window is focused.
bool is_window_focused(const int index);
/// If possible, try to manually focus the window.
void set_window_focused(const int index);
/// Sets the window position to the offset provided.
void set_window_position(const int index, const Offset offset);
/// Sets the window to the specified size. The platform will handle the subsequent resize events.
void set_window_size(const int index, const Extent extent);
/// Sets the window title.
void set_window_title(const int index, const std::string_view title);
/// Queries whether or not the button is currently pressed.
bool get_key_down(const InputButton key);
/// If available for the InputButton, returns the platform-specific keycode.
int get_keycode(const InputButton key);
/// Returns the current moue cursor position, relative to the window.
Offset get_cursor_position();
/// Returns the current moue cursor position, relative to the monitor.
Offset get_screen_cursor_position();
/// Queries whether or not the mouse button requested is pressed or not.
bool get_mouse_button_down(const int button);
/// Returns the current mouse wheel delta on both axes.
std::tuple<float, float> get_wheel_delta();
/// Returns the current right stick axes values from 0->1
std::tuple<float, float> get_right_stick_position();
/// Returns the current left stick axes values from 0->1
std::tuple<float, float> get_left_stick_position();
/// On platforms that support moue capture, this will lock the mouse cursor to the window and hide it.
void capture_mouse(const bool capture);
/**Opens a file dialog to select a file. This will not block.
@param existing Whether or not to limit to existing files.
@param returnFunction The callback function when a file is selected or the dialog is cancelled. An empy string is returned when cancelled.
@param openDirectory Whether or not to allow selecting directories as well.
*/
void open_dialog(const bool existing, std::function<void(std::string)> returnFunction, bool openDirectory = false);
/**Opens a file dialog to select a save location for a file. This will not block.
@param returnFunction The callback function when a file is selected or the dialog is cancelled. An empy string is returned when cancelled.
*/
void save_dialog(std::function<void(std::string)> returnFunction);
/**Translates a virtual keycode to it's character equivalent.
@note Example: translateKey(0x01) = 'a'; 0x01 in this example is a platform and language specific keycode for a key named 'a'.
@return A char pointer to the string if translated correctly.
@note Manually freeing the string returned is not needed.
*/
char* translate_keycode(const unsigned int keycode);
/// Mute standard output
void mute_output();
/// Unmute standard output
void unmute_output();
}

279
engine/core/include/scene.hpp Executable file
View file

@ -0,0 +1,279 @@
#pragma once
#include <vector>
#include <string>
#include <array>
#include <functional>
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
#include <sol.hpp>
#endif
#include <nlohmann/json.hpp>
#include "vector.hpp"
#include "matrix.hpp"
#include "quaternion.hpp"
#include "utility.hpp"
#include "transform.hpp"
#include "object.hpp"
#include "asset.hpp"
#include "components.hpp"
#include "aabb.hpp"
#include "plane.hpp"
template<class Component>
using Pool = std::unordered_map<Object, Component>;
template<class... Components>
class ObjectComponents : Pool<Components>... {
public:
/** Adds a new object.
@param parent The object to parent the new object to. Default is null.
@return The newly created object. Will not be null.
*/
Object add_object(const Object parent = NullObject) {
const Object new_index = make_unique_index();
add<Data>(new_index).parent = parent;
add<Transform>(new_index);
_objects.push_back(new_index);
return new_index;
}
/// Adds a new object but with a manually specified id.
Object add_object_by_id(const Object id) {
add<Data>(id);
add<Transform>(id);
_objects.push_back(id);
return id;
}
/// Remove an object.
void remove_object(const Object obj) {
recurse_remove(obj);
}
/// Find an object by name.
Object find_object(const std::string_view name) const {
for(auto& obj : _objects) {
if(get<Data>(obj).name == name)
return obj;
}
return NullObject;
}
/// Check whether or not an object exists.
bool object_exists(const std::string_view name) const {
return find_object(name) != NullObject;
}
/// Check if the object originated from a prefab. This works even on children of a root prefab object.
bool is_object_prefab(const Object obj) const {
bool is_prefab = false;
check_prefab_parent(obj, is_prefab);
return is_prefab;
}
/// Duplicates an object and all of it's components and data.
Object duplicate_object(const Object original_object) {
const Object duplicate_object = make_unique_index();
(add_duplicate_component<Components>(original_object, duplicate_object), ...);
_objects.push_back(duplicate_object);
return duplicate_object;
}
/// Returns all of the children of an object, with optional recursion.
std::vector<Object> children_of(const Object obj, const bool recursive = false) const {
std::vector<Object> vec;
if(recursive) {
recurse_children(vec, obj);
} else {
for(auto& o : _objects) {
if(get(o).parent == obj)
vec.push_back(o);
}
}
return vec;
}
/// Adds a component.
template<class Component>
Component& add(const Object object) {
return Pool<Component>::emplace(object, Component()).first->second;
}
/// Returns a component.
template<class Component = Data>
Component& get(const Object object) {
return Pool<Component>::at(object);
}
/// Returns a component.
template<class Component = Data>
Component get(const Object object) const {
return Pool<Component>::at(object);
}
/// Checks whether or not an object has a certain component.
template<class Component>
bool has(const Object object) const {
return Pool<Component>::count(object);
}
/// Removes a component from an object. Is a no-op if the component wasn't attached.
template<class Component>
bool remove(const Object object) {
if(has<Component>(object)) {
Pool<Component>::erase(object);
return true;
} else {
return false;
}
}
/// Returns all instances of a component.
template<class Component>
std::vector<std::tuple<Object, Component&>> get_all() {
std::vector<std::tuple<Object, Component&>> comps;
for(auto it = Pool<Component>::begin(); it != Pool<Component>::end(); it++)
comps.emplace_back(it->first, it->second);
return comps;
}
/// Returns all objects.
std::vector<Object> get_objects() const {
return _objects;
}
/// Callback function when an object is removed.
std::function<void(Object)> on_remove;
private:
Object make_unique_index() {
return _last_index++;
}
void recurse_remove(Object obj) {
for(auto& o : children_of(obj))
recurse_remove(o);
utility::erase(_objects, obj);
on_remove(obj);
(remove<Components>(obj), ...);
}
void check_prefab_parent(Object obj, bool& p) const {
if(!get(obj).prefab_path.empty())
p = true;
if(get(obj).parent != NullObject)
check_prefab_parent(get(obj).parent, p);
}
template<class Component>
void add_duplicate_component(const Object from, const Object to) {
if(Pool<Component>::count(from))
Pool<Component>::emplace(to, Pool<Component>::at(from));
}
void recurse_children(std::vector<Object>& vec, Object obj) const {
for(const auto o : _objects) {
if(get(o).parent == obj) {
vec.push_back(o);
recurse_children(vec, o);
}
}
}
Object _last_index = 1;
std::vector<Object> _objects;
};
const int max_spot_shadows = 4;
const int max_point_shadows = 4;
const int max_environment_probes = 4;
class GFXFramebuffer;
/// Represents a scene consisting of Objects with varying Components.
class Scene : public ObjectComponents<Data, Transform, Renderable, Light, Camera, Collision, Rigidbody, UI, EnvironmentProbe> {
public:
/// If loaded from disk, the path to the scene file this originated from.
std::string path;
/// Invalidates the current shadow data for all shadowed lights. This may cause stuttering while the data is regenerated.
void reset_shadows() {
sun_light_dirty = true;
for(auto& point_dirty : point_light_dirty)
point_dirty = true;
for(auto& spot_dirty : spot_light_dirty)
spot_dirty = true;
}
/// Invalidates the current environment probe data. This may cause stuttering while the data is regenerated.
void reset_environment() {
for(auto& probe_dirty : environment_dirty)
probe_dirty = true;
}
// shadow
GFXTexture* depthTexture = nullptr;
GFXFramebuffer* framebuffer = nullptr;
Matrix4x4 lightSpace;
Matrix4x4 spotLightSpaces[max_spot_shadows];
GFXTexture* pointLightArray = nullptr;
GFXTexture* spotLightArray = nullptr;
bool sun_light_dirty = false;
std::array<bool, max_point_shadows> point_light_dirty;
std::array<bool, max_spot_shadows> spot_light_dirty;
// environment
std::array<bool, max_environment_probes> environment_dirty;
GFXTexture *irradianceCubeArray = nullptr, *prefilteredCubeArray = nullptr;
// script
std::string script_path;
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
sol::environment env;
#endif
};
/** Positions and rotates a camera to look at a target from a position.
@param scene The scene that the camera exists in.
@param cam The camera object.
@param pos The position the camera will be viewing the target from.
@param target The position to be centered in the camera's view.
@note Although this is a look at function, it applies no special attribute to the camera and simply changes it's position and rotation.
*/
inline void camera_look_at(Scene& scene, Object cam, Vector3 pos, Vector3 target) {
scene.get<Transform>(cam).position = pos;
scene.get<Transform>(cam).rotation = transform::quat_look_at(pos, target, Vector3(0, 1, 0));
}
Object load_object(Scene& scene, const nlohmann::json obj);
nlohmann::json save_object(Object obj);

52
engine/core/include/screen.hpp Executable file
View file

@ -0,0 +1,52 @@
#pragma once
#include <string>
#include <vector>
#include <map>
#include <functional>
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
#include <sol.hpp>
#endif
#include "uielement.hpp"
#include "file.hpp"
class GFXBuffer;
namespace ui {
class Screen {
public:
Screen() {}
Screen(const file::Path path);
void process_event(const std::string& type, std::string data = "");
void process_mouse(const int x, const int y);
void calculate_sizes();
std::vector<UIElement> elements;
UIElement* find_element(const std::string& id);
using CallbackFunction = std::function<void()>;
std::map<std::string, CallbackFunction> listeners;
bool blurs_background = false;
void add_listener(const std::string& id, std::function<void()> callback);
Extent extent;
bool view_changed = false;
GFXBuffer* glyph_buffer = nullptr;
GFXBuffer* instance_buffer = nullptr;
GFXBuffer* elements_buffer = nullptr;
std::string script_path;
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
sol::environment env;
#endif
};
}

View file

@ -0,0 +1,67 @@
#pragma once
#include <string>
#include "asset.hpp"
enum class MetricType {
Absolute,
Relative,
Offset
};
struct Color {
Color() : r(1.0f), g(1.0f), b(1.0f), a(1.0f) {}
Color(const float v) : r(v), g(v), b(v), a(1.0f) {}
union {
struct {
float r, g, b, a;
};
float v[4];
};
};
class GFXTexture;
class Texture;
class UIElement {
public:
struct Metrics {
struct Metric {
MetricType type = MetricType::Absolute;
int value = 0;
};
Metric x, y, width, height;
} metrics;
struct Background {
Color color = Color(0.0f);
std::string image;
AssetPtr<Texture> texture;
} background;
enum class TextLocation {
TopLeft,
Center
} text_location = TextLocation::TopLeft;
std::string id, text, parent;
bool wrap_text = false;
float absolute_x = 0.0f, absolute_y = 0.0f, absolute_width = 0.0f, absolute_height = 0.0f;
float text_x = 0.0f, text_y = 0.0f;
bool visible = true;
std::string on_click_script;
};
inline bool operator==(const UIElement& a, const UIElement& b) {
return a.id == b.id;
}

155
engine/core/src/debug.cpp Normal file
View file

@ -0,0 +1,155 @@
#include "debug.hpp"
#include <imgui.h>
#include <imgui_stdlib.h>
#include "engine.hpp"
#include "shadowpass.hpp"
#include "scenecapture.hpp"
#include "gfx.hpp"
#include "asset.hpp"
#include "imgui_utility.hpp"
void draw_general() {
ImGui::Text("Platform: %s", platform::get_name());
ImGui::Text("GFX: %s", engine->get_gfx()->get_name());
}
void draw_asset() {
ImGui::Text("Asset References");
ImGui::Separator();
ImGui::BeginChild("asset_child", ImVec2(-1, -1), true);
for(auto& [path, block] : assetm->reference_blocks) {
ImGui::PushID(&block);
ImGui::Text("- %s has %llu reference(s)", path.c_str(), block->references);
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor(200, 0, 0));
if(ImGui::SmallButton("Force unload"))
block->references = 0;
ImGui::PopStyleColor();
ImGui::PopID();
}
ImGui::EndChild();
}
void draw_lighting() {
if(engine->get_scene() != nullptr) {
const auto& lights = engine->get_scene()->get_all<Light>();
ImGui::Text("Lights");
ImGui::Separator();
for(auto& [obj, light] : lights) {
auto& transform = engine->get_scene()->get<Transform>(obj);
ImGui::DragFloat3((engine->get_scene()->get(obj).name + "Position").c_str(), transform.position.ptr());
}
ImGui::Text("Environment");
ImGui::Separator();
if(ImGui::Button("Reload shadows")) {
engine->get_scene()->reset_shadows();
}
if(ImGui::Button("Reload probes")) {
engine->get_scene()->reset_environment();
}
} else {
ImGui::TextDisabled("No scene loaded.");
}
}
void draw_renderer() {
if(engine->get_scene() != nullptr) {
ImGui::Text("Budgets");
ImGui::Separator();
const auto& lights = engine->get_scene()->get_all<Light>();
const auto& probes = engine->get_scene()->get_all<EnvironmentProbe>();
const auto& renderables = engine->get_scene()->get_all<Renderable>();
ImGui::ProgressBar("Light Budget", lights.size(), max_scene_lights);
ImGui::ProgressBar("Probe Budget", probes.size(), max_environment_probes);
int material_count = 0;
for(const auto& [obj, renderable] : renderables) {
for(auto& material : renderable.materials) {
if(material)
material_count++;
}
}
ImGui::ProgressBar("Material Budget", material_count, max_scene_materials);
} else {
ImGui::TextDisabled("No scene loaded for budgeting.");
}
ImGui::Text("Performance");
ImGui::Separator();
ImGui::Text("FPS: %f", ImGui::GetIO().Framerate);
ImGui::Text("Options");
ImGui::Separator();
bool should_recompile = false;
float render_scale = render_options.render_scale;
if(ImGui::DragFloat("Render Scale", &render_scale, 0.1f, 1.0f, 0.1f) && render_scale > 0.0f) {
render_options.render_scale = render_scale;
engine->get_renderer()->resize(engine->get_renderer()->get_extent());
}
if(ImGui::InputInt("Shadow Resolution", &render_options.shadow_resolution)) {
engine->get_renderer()->shadow_pass->create_scene_resources(*engine->get_scene());
engine->get_scene()->reset_shadows();
}
ImGui::Checkbox("Enable AA", &render_options.enable_aa);
should_recompile |= ImGui::Checkbox("Enable IBL", &render_options.enable_ibl);
should_recompile |= ImGui::Checkbox("Enable Normal Mapping", &render_options.enable_normal_mapping);
should_recompile |= ImGui::Checkbox("Enable Normal Shadowing", &render_options.enable_normal_shadowing);
should_recompile |= ImGui::Checkbox("Enable Point Shadows", &render_options.enable_point_shadows);
should_recompile |= ImGui::ComboEnum("Shadow Filter", &render_options.shadow_filter);
ImGui::Checkbox("Enable Extra Passes", &render_options.enable_extra_passes);
ImGui::Checkbox("Enable Frustum Culling", &render_options.enable_frustum_culling);
if(ImGui::Button("Force recompile materials (needed for some render option changes!)") || should_recompile) {
for(auto material : assetm->get_all<Material>()) {
material->capture_pipeline = nullptr;
material->skinned_pipeline = nullptr;
material->static_pipeline = nullptr;
}
}
}
void draw_debug_ui() {
if(ImGui::Begin("General"))
draw_general();
ImGui::End();
if(ImGui::Begin("Lighting"))
draw_lighting();
ImGui::End();
if(ImGui::Begin("Assets"))
draw_asset();
ImGui::End();
if(ImGui::Begin("Rendering"))
draw_renderer();
ImGui::End();
}

901
engine/core/src/engine.cpp Executable file
View file

@ -0,0 +1,901 @@
#include "engine.hpp"
#include <nlohmann/json.hpp>
#include "timer.hpp"
#include "gfx.hpp"
#include "renderer.hpp"
#include "screen.hpp"
#include "file.hpp"
#include "transform.hpp"
#include "math.hpp"
#include "cutscene.hpp"
#include "log.hpp"
#include "imguilayer.hpp"
#include "app.hpp"
#include "json_conversions.hpp"
#include "debug.hpp"
#include "assertions.hpp"
Engine::Engine(const int argc, char* argv[]) {
console::info(System::Core, "Prism Engine loading...");
for(int i = 0; i < argc; i++)
command_line_arguments.push_back(argv[i]);
_input = std::make_unique<Input>();
_physics = std::make_unique<Physics>();
_imgui = std::make_unique<ImGuiLayer>();
assetm = std::make_unique<AssetManager>();
create_lua_interface();
}
void Engine::set_app(App* app) {
Expects(app != nullptr);
_app = app;
}
App* Engine::get_app() const {
return _app;
}
void Engine::load_localization(const std::string_view path) {
Expects(!path.empty());
auto file = file::open(file::app_domain / path);
if(file.has_value()) {
nlohmann::json j;
file->read_as_stream() >> j;
_strings = j["strings"].get<std::map<std::string, std::string>>();
}
}
void Engine::pause() {
_paused = true;
push_event("engine_pause");
}
void Engine::unpause() {
_paused = false;
push_event("engine_unpause");
}
bool Engine::is_paused() const {
return _paused;
}
void Engine::quit() {
_windows[0].quitRequested = true;
}
bool Engine::is_quitting() const {
return _windows[0].quitRequested;
}
void Engine::prepare_quit() {
_app->prepare_quit();
}
void Engine::set_gfx(GFX* gfx) {
_gfx = gfx;
}
GFX* Engine::get_gfx() {
return _gfx;
}
Input* Engine::get_input() {
return _input.get();
}
Renderer* Engine::get_renderer(const int index) {
return _windows[index].renderer.get();
}
Physics* Engine::get_physics() {
return _physics.get();
}
void Engine::create_empty_scene() {
auto scene = std::make_unique<Scene>();
setup_scene(*scene);
_scenes.push_back(std::move(scene));
_current_scene = _scenes.back().get();
}
Scene* Engine::load_scene(const file::Path path) {
Expects(!path.empty());
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Core, "Failed to load scene from {}!", path);
return nullptr;
}
nlohmann::json j;
file->read_as_stream() >> j;
auto scene = std::make_unique<Scene>();
if(j.contains("script"))
scene->script_path = j["script"];
std::map<Object, std::string> parentQueue;
for(auto& obj : j["objects"]) {
if(obj.contains("prefabPath")) {
Object o = add_prefab(*scene, file::app_domain / obj["prefabPath"].get<std::string_view>());
scene->get(o).name = obj["name"];
auto& transform = scene->get<Transform>(o);
transform.position = obj["position"];
transform.rotation = obj["rotation"];
transform.scale = obj["scale"];
} else {
auto o = load_object(*scene.get(), obj);
if(obj.contains("parent"))
parentQueue[o] = obj["parent"];
}
}
for(auto& [obj, toParent] : parentQueue)
scene->get<Data>(obj).parent = scene->find_object(toParent);
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
scene->env = sol::environment(lua, sol::create, lua.globals());
scene->env["scene"] = scene.get();
if(!scene->script_path.empty()) {
auto script_file = file::open(file::root_path(path) / scene->script_path);
if(script_file != std::nullopt)
engine->lua.safe_script(script_file->read_as_string(), scene->env);
}
#endif
setup_scene(*scene);
_scenes.push_back(std::move(scene));
_current_scene = _scenes.back().get();
_path_to_scene[path] = _scenes.back().get();
return _scenes.back().get();
}
void Engine::save_scene(const std::string_view path) {
Expects(!path.empty());
nlohmann::json j;
for(auto& obj : _current_scene->get_objects()) {
if(!_current_scene->get(obj).editor_object)
j["objects"].push_back(save_object(obj));
}
j["script"] = _current_scene->script_path;
std::ofstream out(path.data());
out << j;
}
ui::Screen* Engine::load_screen(const file::Path path) {
Expects(!path.empty());
return new ui::Screen(path);
}
void Engine::set_screen(ui::Screen* screen) {
_current_screen = screen;
screen->extent = _windows[0].extent;
screen->calculate_sizes();
get_renderer()->set_screen(screen);
}
ui::Screen* Engine::get_screen() const {
return _current_screen;
}
AnimationChannel Engine::load_animation(nlohmann::json a) {
AnimationChannel animation;
for(auto& kf : a["frames"]) {
PositionKeyFrame keyframe;
keyframe.time = kf["time"];
keyframe.value = kf["value"];
animation.positions.push_back(keyframe);
}
return animation;
}
Animation Engine::load_animation(const file::Path path) {
Expects(!path.empty());
auto file = file::open(path, true);
if(!file.has_value()) {
console::error(System::Core, "Failed to load animation from {}!", path);
return {};
}
Animation anim;
file->read(&anim.duration);
file->read(&anim.ticks_per_second);
unsigned int num_channels;
file->read(&num_channels);
for(unsigned int i = 0; i < num_channels; i++) {
AnimationChannel channel;
file->read_string(channel.id);
unsigned int num_positions;
file->read(&num_positions);
for(unsigned int j = 0; j < num_positions; j++) {
PositionKeyFrame key;
file->read(&key);
channel.positions.push_back(key);
}
unsigned int num_rotations;
file->read(&num_rotations);
for(unsigned int j = 0; j < num_rotations; j++) {
RotationKeyFrame key;
file->read(&key);
channel.rotations.push_back(key);
}
unsigned int num_scales;
file->read(&num_scales);
for(unsigned int j = 0; j < num_scales; j++) {
ScaleKeyFrame key;
file->read(&key);
channel.scales.push_back(key);
}
anim.channels.push_back(channel);
}
return anim;
}
void Engine::load_cutscene(const file::Path path) {
Expects(!path.empty());
cutscene = std::make_unique<Cutscene>();
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Core, "Failed to load cutscene from {}!", path);
return;
}
nlohmann::json j;
file->read_as_stream() >> j;
for(auto& s : j["shots"]) {
Shot shot;
shot.begin = s["begin"];
shot.length = s["end"];
for(auto& animation : s["animations"]) {
shot.channels.push_back(load_animation(animation));
}
if(_path_to_scene.count(s["scene"])) {
shot.scene = _path_to_scene[s["scene"]];
// try to find main camera
auto [obj, cam] = shot.scene->get_all<Camera>()[0];
for(auto& anim : shot.channels) {
if(anim.target == NullObject)
anim.target = obj;
}
} else {
load_scene(file::root_path(path) / s["scene"].get<std::string_view>());
if(engine->get_scene() == nullptr)
engine->create_empty_scene();
auto cameraObj = engine->get_scene()->add_object();
engine->get_scene()->add<Camera>(cameraObj);
for(auto& anim : shot.channels) {
if(anim.target == NullObject)
anim.target = cameraObj;
}
_path_to_scene[s["scene"]] = _current_scene;
shot.scene = _current_scene;
}
cutscene->shots.push_back(shot);
}
}
void Engine::save_cutscene(const std::string_view path) {
Expects(!path.empty());
nlohmann::json j;
for(auto& shot : engine->cutscene->shots) {
nlohmann::json s;
s["begin"] = shot.begin;
s["end"] = shot.length;
for(auto& [path, scene] : _path_to_scene) {
if(shot.scene == scene)
s["scene"] = path;
}
j["shots"].push_back(s);
}
std::ofstream out(path);
out << j;
}
Object Engine::add_prefab(Scene& scene, const file::Path path, const std::string_view override_name) {
Expects(!path.empty());
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Core, "Failed to load prefab from {}!", path);
return NullObject;
}
nlohmann::json j;
file->read_as_stream() >> j;
Object root_node = NullObject;
std::map<Object, std::string> parent_queue;
for(const auto obj : j["objects"]) {
auto o = load_object(scene, obj);
if(obj.contains("parent")) {
parent_queue[o] = obj["parent"];
} else {
root_node = o;
}
}
for(auto& [obj, parent_name] : parent_queue)
scene.get(obj).parent = scene.find_object(parent_name);
if(!override_name.empty() && root_node != NullObject)
scene.get(root_node).name = override_name;
return root_node;
}
void Engine::save_prefab(const Object root, const std::string_view path) {
Expects(root != NullObject);
Expects(!path.empty());
nlohmann::json j;
for(auto& obj : _current_scene->get_objects()) {
if(!_current_scene->get(obj).editor_object)
j["objects"].push_back(save_object(obj));
}
std::ofstream out(path.data());
out << j;
}
void Engine::add_window(void* native_handle, const int identifier, const Extent extent) {
Expects(native_handle != nullptr);
Expects(identifier >= 0);
const auto drawable_extent = platform::get_window_drawable_size(identifier);
_gfx->initialize_view(native_handle, identifier, drawable_extent.width, drawable_extent.height);
Window window;
window.identifier = identifier;
window.extent = extent;
window.renderer = std::make_unique<Renderer>(_gfx);
window.renderer->resize(drawable_extent);
render_ready = true;
_windows.push_back(std::move(window));
}
void Engine::remove_window(const int identifier) {
Expects(identifier >= 0);
utility::erase_if(_windows, [identifier](Window& w) {
return w.identifier == identifier;
});
}
void Engine::resize(const int identifier, const Extent extent) {
Expects(identifier >= 0);
auto window = get_window(identifier);
Expects(window != nullptr);
window->extent = extent;
const auto drawable_extent = platform::get_window_drawable_size(identifier);
_gfx->recreate_view(identifier, drawable_extent.width, drawable_extent.height);
window->renderer->resize(drawable_extent);
if(identifier == 0) {
if(_current_screen != nullptr) {
_current_screen->extent = extent;
_current_screen->calculate_sizes();
}
}
}
void Engine::process_key_down(const unsigned int keyCode) {
Expects(keyCode >= 0);
_imgui->process_key_down(keyCode);
if(keyCode == platform::get_keycode(debug_button))
debug_enabled = !debug_enabled;
}
void Engine::process_key_up(const unsigned int keyCode) {
Expects(keyCode >= 0);
_imgui->process_key_up(keyCode);
}
void Engine::process_mouse_down(const int button, const Offset offset) {
Expects(offset.x >= 0);
Expects(offset.y >= 0);
if(_current_screen != nullptr && button == 0)
_current_screen->process_mouse(offset.x, offset.y);
}
void Engine::push_event(const std::string_view name, const std::string_view data) {
Expects(!name.empty());
if(_current_screen != nullptr)
_current_screen->process_event(name.data(), data.data());
}
bool Engine::has_localization(const std::string_view id) const {
Expects(!id.empty());
return _strings.count(id.data());
}
std::string Engine::localize(const std::string id) {
Expects(!id.empty());
return _strings[id];
}
void Engine::calculate_bone(Mesh& mesh, const Mesh::Part& part, Bone& bone, const Bone* parent_bone) {
if(part.offset_matrices.empty())
return;
Matrix4x4 parent_matrix;
if(parent_bone != nullptr)
parent_matrix = parent_bone->local_transform;
Matrix4x4 local = transform::translate(Matrix4x4(), bone.position);
local *= matrix_from_quat(bone.rotation);
local = transform::scale(local, bone.scale);
bone.local_transform = parent_matrix * local;
bone.final_transform = mesh.global_inverse_transformation * bone.local_transform * part.offset_matrices[bone.index];
for(auto& b : mesh.bones) {
if(b.parent != nullptr && b.parent->index == bone.index)
calculate_bone(mesh, part, b, &bone);
}
}
void Engine::calculate_object(Scene& scene, Object object, const Object parent_object) {
Matrix4x4 parent_matrix;
if(parent_object != NullObject)
parent_matrix = scene.get<Transform>(parent_object).model;
auto& transform = scene.get<Transform>(object);
Matrix4x4 local = transform::translate(Matrix4x4(), transform.position);
local *= matrix_from_quat(transform.rotation);
local = transform::scale(local, transform.scale);
transform.model = parent_matrix * local;
if(scene.has<Renderable>(object)) {
auto& mesh = scene.get<Renderable>(object);
if(mesh.mesh && !mesh.mesh->bones.empty()) {
for(auto& part : mesh.mesh->parts) {
std::vector<Matrix4x4> bones(mesh.mesh->bones.size());
if(scene.get(object).parent != NullObject && scene.has<Renderable>(scene.get(object).parent) && !scene.get<Renderable>(scene.get(object).parent).mesh->bones.empty()) {
for(auto [i, ourBone] : utility::enumerate(mesh.mesh->bones)) {
for(auto& theirBone : scene.get<Renderable>(scene.get(object).parent).mesh->bones) {
if(ourBone.name == theirBone.name)
bones[i] = mesh.mesh->global_inverse_transformation * theirBone.local_transform * part.offset_matrices[ourBone.index];
}
}
} else {
calculate_bone(*mesh.mesh.handle, part, *mesh.mesh->root_bone);
for(auto [i, bone] : utility::enumerate(bones))
bones[i] = mesh.mesh->bones[i].final_transform;
}
engine->get_gfx()->copy_buffer(part.bone_batrix_buffer, bones.data(), 0, bones.size() * sizeof(Matrix4x4));
}
}
}
for(auto& child : scene.children_of(object))
calculate_object(scene, child, object);
}
Shot* Engine::get_shot(const float time) {
for(auto& shot : engine->cutscene->shots) {
if(time >= shot.begin && time <= (shot.begin + shot.length))
return &shot;
}
return nullptr;
}
void Engine::update_animation_channel(Scene& scene, const AnimationChannel& channel, const float time) {
{
int keyframeIndex = -1;
int i = 0;
for(auto& frame : channel.positions) {
if(time >= frame.time)
keyframeIndex = i;
i++;
}
if(keyframeIndex != -1) {
Vector3* targetVec = nullptr;
if(channel.bone != nullptr)
targetVec = &channel.bone->position;
if(channel.target != NullObject && scene.has<Data>(channel.target))
targetVec = &scene.get<Transform>(channel.target).position;
auto& startFrame = channel.positions[keyframeIndex];
int endFrameIndex = keyframeIndex + 1;
if(endFrameIndex > channel.positions.size() - 1) {
if(targetVec != nullptr)
*targetVec = startFrame.value;
} else {
auto& endFrame = channel.positions[endFrameIndex];
if(targetVec != nullptr)
*targetVec = lerp(startFrame.value, endFrame.value, (float)(time - startFrame.time) / (float)(endFrame.time - startFrame.time));
}
}
}
{
int keyframeIndex = -1;
int i = 0;
for(auto& frame : channel.rotations) {
if(time >= frame.time)
keyframeIndex = i;
i++;
}
if(keyframeIndex != -1) {
Quaternion* targetVec = nullptr;
if(channel.bone != nullptr)
targetVec = &channel.bone->rotation;
auto& startFrame = channel.rotations[keyframeIndex];
int endFrameIndex = keyframeIndex + 1;
if(endFrameIndex > channel.rotations.size() - 1) {
if(targetVec != nullptr)
*targetVec = startFrame.value;
} else {
auto& endFrame = channel.rotations[endFrameIndex];
if(targetVec != nullptr)
*targetVec = lerp(startFrame.value, endFrame.value, (float)(time - startFrame.time) / (float)(endFrame.time - startFrame.time));
}
}
}
}
void Engine::update_cutscene(const float time) {
Shot* currentShot = get_shot(time);
if(currentShot != nullptr) {
_current_scene = currentShot->scene;
for(auto& channel : currentShot->channels)
update_animation_channel(*_current_scene, channel, time);
} else {
_current_scene = nullptr;
}
}
void Engine::update_animation(const Animation& anim, const float time) {
for(const auto& channel : anim.channels)
update_animation_channel(*_current_scene, channel, time);
}
void Engine::begin_frame(const float delta_time) {
_imgui->begin_frame(delta_time);
if(debug_enabled)
draw_debug_ui();
if(_app != nullptr)
_app->begin_frame();
}
void Engine::update(const float delta_time) {
const float ideal_delta_time = 0.01667f;
if(render_options.dynamic_resolution) {
if(delta_time < ideal_delta_time) {
render_options.render_scale -= 0.1f;
render_options.render_scale = std::fmax(render_options.render_scale, 0.1f);
}
}
for(auto& timer : _timers) {
if(!_paused || (_paused && timer->continue_during_pause))
timer->current_time += delta_time;
if(timer->current_time >= timer->duration) {
timer->callback();
timer->current_time = 0.0f;
if(timer->remove_on_trigger)
_timersToRemove.push_back(timer);
}
}
for(auto& timer : _timersToRemove)
utility::erase(_timers, timer);
_timersToRemove.clear();
_input->update();
_app->update(delta_time);
if(_current_scene != nullptr) {
if(update_physics && !_paused)
_physics->update(delta_time);
if(cutscene != nullptr && play_cutscene && !_paused) {
update_cutscene(current_cutscene_time);
current_cutscene_time += delta_time;
}
for(auto& target : _animation_targets) {
if((target.current_time * target.animation.ticks_per_second) > target.animation.duration) {
if(target.looping) {
target.current_time = 0.0f;
} else {
utility::erase(_animation_targets, target);
}
} else {
update_animation(target.animation, target.current_time * target.animation.ticks_per_second);
target.current_time += delta_time * target.animation_speed_modifier;
}
}
update_scene(*_current_scene);
}
assetm->perform_cleanup();
}
void Engine::update_scene(Scene& scene) {
for(auto& obj : scene.get_objects()) {
if(scene.get(obj).parent == NullObject)
calculate_object(scene, obj);
}
}
void Engine::render(const int index) {
Expects(index >= 0);
auto window = get_window(index);
if(window == nullptr)
return;
if(index == 0) {
if(_current_screen != nullptr && _current_screen->view_changed) {
_windows[0].renderer->update_screen();
_current_screen->view_changed = false;
}
_imgui->render(0);
}
if(window->renderer != nullptr)
window->renderer->render(_current_scene, index);
}
void Engine::add_timer(Timer& timer) {
_timers.push_back(&timer);
}
Scene* Engine::get_scene() {
return _current_scene;
}
Scene* Engine::get_scene(const std::string_view name) {
Expects(!name.empty());
return _path_to_scene[name.data()];
}
void Engine::set_current_scene(Scene* scene) {
_current_scene = scene;
}
std::string_view Engine::get_scene_path() const {
for(auto& [path, scene] : _path_to_scene) {
if(scene == this->_current_scene)
return path;
}
return "";
}
void Engine::on_remove(Object object) {
Expects(object != NullObject);
_physics->remove_object(object);
}
void Engine::play_animation(Animation animation, Object object, bool looping) {
Expects(object != NullObject);
if(!_current_scene->has<Renderable>(object))
return;
AnimationTarget target;
target.animation = animation;
target.target = object;
target.looping = looping;
for(auto& channel : target.animation.channels) {
for(auto& bone : _current_scene->get<Renderable>(object).mesh->bones) {
if(channel.id == bone.name)
channel.bone = &bone;
}
}
_animation_targets.push_back(target);
}
void Engine::set_animation_speed_modifier(Object target, float modifier) {
for(auto& animation : _animation_targets) {
if(animation.target == target)
animation.animation_speed_modifier = modifier;
}
}
void Engine::stop_animation(Object target) {
for(auto& animation : _animation_targets) {
if(animation.target == target)
utility::erase(_animation_targets, animation);
}
}
void Engine::create_lua_interface() {
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
lua.open_libraries(sol::lib::base, sol::lib::package, sol::lib::table, sol::lib::string, sol::lib::math);
lua.new_usertype<Cutscene>("Cutscene");
lua.new_usertype<InputBindingData>("InputBindingData",
"name", &InputBindingData::name);
lua.new_usertype<Input>("Input",
"get_bindings", sol::readonly_property(&Input::get_bindings));
lua.new_usertype<Engine>("Engine",
"pause", &Engine::pause,
"unpause", &Engine::unpause,
"clear_cutscene", [this] {
cutscene = nullptr;
},
"update_screen", [this] {
get_renderer()->update_screen();
},
"quit", &Engine::quit,
"get_input", sol::readonly_property(&Engine::get_input),
"push_event", &Engine::push_event);
lua["engine"] = this;
lua.new_usertype<ui::Screen>("UIScreen",
"find_element", &ui::Screen::find_element,
"add_element", [](ui::Screen* screen, UIElement& element) {
screen->elements.push_back(element);
});
lua.new_usertype<UIElement::Background>("UIElementBackground",
"image", &UIElement::Background::image);
lua.new_usertype<UIElement::UIElement::Metrics::Metric>("UIElementMetric",
"value", &UIElement::Metrics::Metric::value);
lua.new_usertype<UIElement::UIElement::Metrics>("UIElementMetrics",
"x", &UIElement::Metrics::x,
"y", &UIElement::Metrics::y,
"width", &UIElement::Metrics::width,
"height", &UIElement::Metrics::height);
lua.new_usertype<UIElement>("UIElement",
"id", &UIElement::id,
"text", &UIElement::text,
"=background", &UIElement::background,
"visible", &UIElement::visible,
"metrics", &UIElement::metrics);
lua.new_usertype<Scene>("Scene");
lua["get_transform"] = [](Object o) -> Transform& {
return engine->get_scene()->get<Transform>(o);
};
lua.new_usertype<Transform>("Transform",
"position", &Transform::position);
lua.new_usertype<Vector3>("Vector3",
"x", &Vector3::x,
"y", &Vector3::y,
"z", &Vector3::z);
#endif
}
void Engine::setup_scene(Scene& scene) {
_physics->reset();
scene.on_remove = [this](Object obj) {
on_remove(obj);
};
get_renderer()->shadow_pass->create_scene_resources(scene);
get_renderer()->scene_capture->create_scene_resources(scene);
scene.reset_shadows();
scene.reset_environment();
}

41
engine/core/src/file.cpp Executable file
View file

@ -0,0 +1,41 @@
#include "file.hpp"
#include "string_utils.hpp"
#include "log.hpp"
#include "assertions.hpp"
file::Path file::root_path(const Path path) {
auto p = path;
while(p.parent_path() != p && p.parent_path() != "/") {
p = p.parent_path();
}
return p;
}
std::optional<file::File> file::open(const file::Path path, const bool binary_mode) {
Expects(!path.empty());
auto fixed_path = path;
if(root_path(path) == app_domain) {
fixed_path = domain_data[static_cast<int>(Domain::App)] / path.lexically_relative(root_path(path));
} else if(root_path(path) == internal_domain) {
fixed_path = domain_data[static_cast<int>(Domain::Internal)] / path.lexically_relative(root_path(path));
}
FILE* file = fopen(fixed_path.c_str(), binary_mode ? "rb" : "r");
if(file == nullptr) {
console::error(System::File, "Failed to open file handle from {}!", path);
return {};
}
return file::File(file);
}
file::Path file::get_domain_path(const Domain domain) {
return domain_data[static_cast<int>(domain)];
}
file::Path parent_domain(const file::Path path) {
return path;
}

211
engine/core/src/imguilayer.cpp Executable file
View file

@ -0,0 +1,211 @@
#include "imguilayer.hpp"
#include <imgui.h>
#include <imgui_stdlib.h>
#include "engine.hpp"
#include "platform.hpp"
#include "assertions.hpp"
const std::map<ImGuiKey, InputButton> imToPl = {
{ImGuiKey_Tab, InputButton::Tab},
{ImGuiKey_LeftArrow, InputButton::LeftArrow},
{ImGuiKey_RightArrow, InputButton::RightArrow},
{ImGuiKey_UpArrow, InputButton::Escape},
{ImGuiKey_DownArrow, InputButton::Escape},
{ImGuiKey_PageUp, InputButton::Escape},
{ImGuiKey_PageDown, InputButton::Escape},
{ImGuiKey_Home, InputButton::Escape},
{ImGuiKey_End, InputButton::Escape},
{ImGuiKey_Insert, InputButton::Escape},
{ImGuiKey_Delete, InputButton::Escape},
{ImGuiKey_Backspace, InputButton::Backspace},
{ImGuiKey_Space, InputButton::Space},
{ImGuiKey_Enter, InputButton::Enter},
{ImGuiKey_Escape, InputButton::Escape},
{ImGuiKey_A, InputButton::A},
{ImGuiKey_C, InputButton::C},
{ImGuiKey_V, InputButton::V},
{ImGuiKey_X, InputButton::X},
{ImGuiKey_Y, InputButton::Y},
{ImGuiKey_Z, InputButton::Z}
};
ImGuiLayer::ImGuiLayer() {
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = "";
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports;
io.BackendPlatformName = "Prism";
//if(platform::supports_feature(PlatformFeature::Windowing))
// io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
for (auto& [im, pl] : imToPl)
io.KeyMap[im] = platform::get_keycode(pl);
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
style.FrameRounding = 3;
style.FrameBorderSize = 0.0f;
style.WindowBorderSize = 0.0f;
style.WindowPadding = ImVec2(10, 10);
style.FramePadding = ImVec2(4, 4);
style.ItemInnerSpacing = ImVec2(5, 0);
style.ItemSpacing = ImVec2(10, 5);
style.ScrollbarSize = 10;
style.GrabMinSize = 6;
style.WindowRounding = 3.0f;
style.ChildRounding = 3.0f;
style.PopupRounding = 3.0f;
style.ScrollbarRounding = 12.0f;
style.GrabRounding = 3.0f;
style.TabRounding = 3.0f;
style.WindowTitleAlign = ImVec2(0.0, 0.5);
ImVec4* colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_FrameBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.68f, 0.68f, 0.68f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.42f, 0.47f, 0.52f, 0.67f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.23f, 0.43f, 0.73f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.74f, 0.80f, 0.87f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.46f, 0.54f, 0.64f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.38f, 0.51f, 0.65f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.57f, 0.61f, 0.67f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.32f, 0.34f, 0.36f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.22f, 0.27f, 0.33f, 0.86f);
colors[ImGuiCol_TabActive] = ImVec4(0.33f, 0.51f, 0.75f, 1.00f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.30f, 0.33f, 0.38f, 1.00f);
ImGuiViewport* main_viewport = ImGui::GetMainViewport();
main_viewport->PlatformHandle = new int(0);
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
ImGuiPlatformMonitor monitor = {};
const auto rect = platform::get_monitor_resolution();
monitor.MainPos = rect.offset;
monitor.MainSize = rect.extent;
const auto wrect = platform::get_monitor_work_area();
monitor.WorkPos = wrect.offset;
monitor.WorkSize = wrect.extent;
monitor.DpiScale = platform::get_monitor_dpi();
platform_io.Monitors.push_back(monitor);
platform_io.Platform_CreateWindow = [](ImGuiViewport* viewport) {
viewport->PlatformHandle = new int(platform::open_window("", {viewport->Pos, viewport->Size}, WindowFlags::Borderless));
};
platform_io.Platform_DestroyWindow = [](ImGuiViewport* viewport) {
platform::close_window(*(int*)viewport->PlatformHandle);
};
platform_io.Platform_ShowWindow = [](ImGuiViewport*) {};
platform_io.Platform_SetWindowPos = [](ImGuiViewport* viewport, const ImVec2 pos) {
platform::set_window_position(*(int*)viewport->PlatformHandle, pos);
};
platform_io.Platform_GetWindowPos = [](ImGuiViewport* viewport) {
auto [x, y] = platform::get_window_position(*(int*)viewport->PlatformHandle);
return ImVec2(x, y);
};
platform_io.Platform_SetWindowSize = [](ImGuiViewport* viewport, const ImVec2 size) {
platform::set_window_size(*(int*)viewport->PlatformHandle, size);
};
platform_io.Platform_GetWindowSize = [](ImGuiViewport* viewport) {
auto [x, y] = platform::get_window_size(*(int*)viewport->PlatformHandle);
return ImVec2(x, y);
};
platform_io.Platform_SetWindowFocus = [](ImGuiViewport* viewport) {
platform::set_window_focused(*(int*)viewport->PlatformHandle);
};
platform_io.Platform_GetWindowFocus = [](ImGuiViewport* viewport) {
return platform::is_window_focused(*(int*)viewport->PlatformHandle);
};
platform_io.Platform_GetWindowMinimized = [](ImGuiViewport*) {
return false;
};
platform_io.Platform_SetWindowTitle = [](ImGuiViewport* viewport, const char* title) {
platform::set_window_title(*(int*)viewport->PlatformHandle, title);
};
}
void ImGuiLayer::begin_frame(const float delta_time) {
ImGuiIO& io = ImGui::GetIO();
const auto [width, height] = platform::get_window_size(0);
const auto [dw, dh] = platform::get_window_drawable_size(0);
io.DisplaySize = ImVec2(width, height);
io.DisplayFramebufferScale = ImVec2((float)dw / width, (float)dh / height);
io.DeltaTime = delta_time;
io.KeyCtrl = platform::get_key_down(InputButton::Ctrl);
io.KeyShift = platform::get_key_down(InputButton::Shift);
io.KeyAlt = platform::get_key_down(InputButton::Alt);
io.KeySuper = platform::get_key_down(InputButton::Super);
io.MouseHoveredViewport = 0;
const auto [sx, sy] = platform::get_wheel_delta();
io.MouseWheel = sy;
io.MouseWheelH = sx;
if(io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
const auto [x, y] = platform::get_screen_cursor_position();
io.MousePos = ImVec2(static_cast<float>(x), static_cast<float>(y));
} else {
const auto [x, y] = platform::get_cursor_position();
io.MousePos = ImVec2(static_cast<float>(x), static_cast<float>(y));
}
io.MouseDown[0] = platform::get_mouse_button_down(0);
io.MouseDown[1] = platform::get_mouse_button_down(1);
ImGui::NewFrame();
}
void ImGuiLayer::render(int index) {
Expects(index >= 0);
if(index == 0) {
ImGui::EndFrame();
ImGui::Render();
ImGui::UpdatePlatformWindows();
}
}
void ImGuiLayer::process_key_down(unsigned int keyCode) {
Expects(keyCode >= 0);
ImGuiIO& io = ImGui::GetIO();
if(keyCode != 52)
io.AddInputCharactersUTF8(platform::translate_keycode(keyCode));
io.KeysDown[keyCode] = true;
}
void ImGuiLayer::process_key_up(unsigned int keyCode) {
Expects(keyCode >= 0);
ImGuiIO& io = ImGui::GetIO();
io.KeysDown[keyCode] = false;
}

155
engine/core/src/input.cpp Executable file
View file

@ -0,0 +1,155 @@
#include "input.hpp"
#include <string>
#include "engine.hpp"
#include "log.hpp"
bool is_in_range(int value, int cond, int range) {
int low = cond - range;
int high = cond + range;
return value >= low && value <= high;
}
void Input::update() {
const auto& [x, y] = platform::get_cursor_position();
auto& [oldX, oldY] = _last_cursor_position;
const auto [width, height] = platform::get_window_size(0);
float xDelta = (x - oldX) / (float)width;
float yDelta = (y - oldY) / (float)height;
if(is_in_range(x, width / 2, 3) && is_in_range(y, height / 2, 3)) {
xDelta = 0.0f;
yDelta = 0.0f;
}
_last_cursor_position = { x, y };
for (auto& binding : _input_bindings) {
binding.value = 0.0f;
for (auto& [key, value] : binding.buttons) {
bool is_pressed = false;
switch(key) {
case InputButton::MouseLeft:
is_pressed = platform::get_mouse_button_down(0);
break;
case InputButton::MouseRight:
is_pressed = platform::get_mouse_button_down(1);
break;
default:
is_pressed = platform::get_key_down(key);
break;
}
if (is_pressed) {
binding.value = value;
if (binding.last_button == key) {
binding.repeat = true;
}
else {
binding.repeat = false;
}
binding.last_button = key;
}
}
if (binding.value == 0.0f) {
binding.last_button = InputButton::Invalid;
binding.repeat = false;
}
for(auto& axis : binding.axises) {
auto [lx, ly] = platform::get_left_stick_position();
auto [rx, ry] = platform::get_right_stick_position();
auto [sx, sy] = platform::get_wheel_delta();
float nextValue = 0.0f;
switch (axis) {
case Axis::MouseX:
nextValue = xDelta;
break;
case Axis::MouseY:
nextValue = yDelta;
break;
case Axis::ScrollX:
nextValue = sx;
break;
case Axis::ScrollY:
nextValue = sy;
break;
case Axis::LeftStickX:
nextValue = lx;
break;
case Axis::LeftStickY:
nextValue = ly;
break;
case Axis::RightStickX:
nextValue = rx;
break;
case Axis::RightStickY:
nextValue = ry;
break;
}
if(nextValue != 0.0f)
binding.value = nextValue;
}
}
}
void Input::add_binding(const std::string& name) {
InputBindingData data;
data.name = name;
_input_bindings.push_back(data);
}
void Input::add_binding_button(const std::string& name, InputButton key, float value) {
for (auto& binding : _input_bindings) {
if (binding.name == name)
binding.buttons[key] = value;
}
}
void Input::add_binding_axis(const std::string& name, Axis axis) {
for (auto& binding : _input_bindings) {
if (binding.name == name) {
binding.axises.push_back(axis);
}
}
}
float Input::get_value(const std::string& name) {
for (auto& binding : _input_bindings) {
if (binding.name == name) {
return binding.value;
}
}
return 0.0f;
}
bool Input::is_pressed(const std::string &name, bool repeating) {
return get_value(name) == 1.0 && (repeating ? true : !is_repeating(name));
}
bool Input::is_repeating(const std::string& name) {
for (auto& binding : _input_bindings) {
if (binding.name == name) {
return binding.repeat;
}
}
return false;
}
std::vector<InputBindingData> Input::get_bindings() const {
return _input_bindings;
}

148
engine/core/src/physics.cpp Executable file
View file

@ -0,0 +1,148 @@
#include "physics.hpp"
#include "engine.hpp"
Physics::Physics() {
reset();
}
void Physics::update(float deltaTime) {
if(engine->get_scene() == nullptr)
return;
for(auto [obj, rigidbody] : engine->get_scene()->get_all<Rigidbody>()) {
if(rigidbody.body != nullptr) {
if(rigidbody.type == Rigidbody::Type::Dynamic) {
if(rigidbody.stored_force != Vector3(0.0f)) {
rigidbody.body->setLinearVelocity(btVector3(rigidbody.stored_force.x, rigidbody.stored_force.y, rigidbody.stored_force.z));
rigidbody.stored_force = Vector3(0.0f);
}
}
}
}
world->stepSimulation(deltaTime);
for(auto [object, collision] : engine->get_scene()->get_all<Collision>()) {
if(collision.shape == nullptr && !collision.is_trigger) {
switch(collision.type) {
case Collision::Type::Cube:
collision.shape = new btBoxShape(btVector3(collision.size.x / 2.0f, collision.size.y / 2.0f, collision.size.z / 2.0f));
break;
case Collision::Type::Capsule:
collision.shape = new btCapsuleShape(0.5f, collision.size.y);
break;
}
}
}
for(auto [obj, rigidbody] : engine->get_scene()->get_all<Rigidbody>()) {
auto& transform = engine->get_scene()->get<Transform>(obj);
if(rigidbody.body == nullptr) {
btTransform t;
t.setIdentity();
t.setOrigin(btVector3(transform.position.x, transform.position.y, transform.position.z));
t.setRotation(btQuaternion(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w));
btDefaultMotionState* motionState = new btDefaultMotionState(t);
btScalar bodyMass = rigidbody.mass;
btVector3 bodyInertia;
if(rigidbody.mass != 0)
engine->get_scene()->get<Collision>(obj).shape->calculateLocalInertia(bodyMass, bodyInertia);
btRigidBody::btRigidBodyConstructionInfo bodyCI = btRigidBody::btRigidBodyConstructionInfo(bodyMass, motionState, engine->get_scene()->get<Collision>(obj).shape, bodyInertia);
bodyCI.m_friction = rigidbody.friction;
rigidbody.body = new btRigidBody(bodyCI);
if(!rigidbody.enable_deactivation)
rigidbody.body->setActivationState(DISABLE_DEACTIVATION);
if(!rigidbody.enable_rotation)
rigidbody.body->setAngularFactor(btVector3(0, 0, 0));
world->addRigidBody(rigidbody.body);
rigidbody.body->setUserIndex(obj);
}
if(rigidbody.type == Rigidbody::Type::Dynamic) {
btTransform trans = rigidbody.body->getWorldTransform();
transform.position = Vector3(trans.getOrigin().x(), trans.getOrigin().y(), trans.getOrigin().z());
} else {
btTransform t;
t.setIdentity();
t.setOrigin(btVector3(transform.position.x, transform.position.y, transform.position.z));
t.setRotation(btQuaternion(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w));
rigidbody.body->setWorldTransform(t);
}
}
}
void Physics::remove_object(Object object) {
if(engine->get_scene()->has<Rigidbody>(object)) {
for(int i = 0 ; i < world->getNumCollisionObjects(); i++) {
auto obj = world->getCollisionObjectArray()[i];
if(obj->getUserIndex() == object) {
world->removeCollisionObject(obj);
delete obj;
}
}
}
}
Physics::RayResult Physics::raycast(Vector3 from, Vector3 to) {
Physics::RayResult result;
btVector3 btFrom(from.x, from.y, from.z);
btVector3 btTo(to.x, to.y, to.z);
btCollisionWorld::AllHitsRayResultCallback res(btFrom, btTo);
world->rayTest(btFrom, btTo, res);
int closestCollisionObject = NullObject;
float closestHitFraction = 1000.0f;
for(int i = 0; i < res.m_collisionObjects.size(); i++) {
if(!engine->get_scene()->get<Collision>(res.m_collisionObjects[i]->getUserIndex()).exclude_from_raycast) {
if(res.m_hitFractions[i] < closestHitFraction) {
closestCollisionObject = i;
closestHitFraction = res.m_hitFractions[i];
}
}
}
if(closestCollisionObject != NullObject) {
result.hasHit = true;
auto vec = res.m_hitPointWorld[closestCollisionObject];;
result.location = Vector3(vec.x(), vec.y(), vec.z());
}
return result;
}
void Physics::reset() {
if(world != nullptr)
world.reset();
broadphase = std::make_unique<btDbvtBroadphase>();
collisionConfiguration = std::make_unique<btDefaultCollisionConfiguration>();
dispatcher = std::make_unique<btCollisionDispatcher>(collisionConfiguration.get());
solver = std::make_unique<btSequentialImpulseConstraintSolver>();
world = std::make_unique<btDiscreteDynamicsWorld>(dispatcher.get(), broadphase.get(), solver.get(), collisionConfiguration.get());
world->setGravity(btVector3(0.0f, -9.8f, 0.0f));
}

202
engine/core/src/scene.cpp Executable file
View file

@ -0,0 +1,202 @@
#include "scene.hpp"
#include "json_conversions.hpp"
#include "file.hpp"
#include "engine.hpp"
void load_transform_component(nlohmann::json j, Transform& t) {
t.position = j["position"];
t.scale = j["scale"];
t.rotation = j["rotation"];
}
void load_renderable_component(nlohmann::json j, Renderable& t) {
if(j.contains("path"))
t.mesh = assetm->get<Mesh>(file::app_domain / j["path"].get<std::string_view>());
for(auto& material : j["materials"])
t.materials.push_back(assetm->get<Material>(file::app_domain / material.get<std::string_view>()));
}
void load_camera_component(nlohmann::json j, Camera& camera) {
if(j.contains("fov"))
camera.fov = j["fov"];
}
void load_light_component(nlohmann::json j, Light& light) {
light.color = j["color"];
light.power = j["power"];
light.type = j["type"];
if(j.count("size") && !j.count("spot_size"))
light.size = j["size"];
else if(j.count("spot_size")) {
light.size = j["size"];
light.spot_size = j["spot_size"];
}
if(j.count("enable_shadows")) {
light.enable_shadows = j["enable_shadows"];
light.use_dynamic_shadows = j["use_dynamic_shadows"];
}
}
void load_collision_component(nlohmann::json j, Collision& collision) {
collision.type = j["type"];
collision.size = j["size"];
if(j.contains("is_trigger")) {
collision.is_trigger = j["is_trigger"];
if(collision.is_trigger)
collision.trigger_id = j["trigger_id"];
}
}
void load_rigidbody_component(nlohmann::json j, Rigidbody& rigidbody) {
rigidbody.type = j["type"];
rigidbody.mass = j["mass"];
}
void load_ui_component(nlohmann::json j, UI& ui) {
ui.width = j["width"];
ui.height = j["height"];
ui.ui_path = j["path"];
}
void load_probe_component(nlohmann::json j, EnvironmentProbe& probe) {
if(j.contains("size"))
probe.size = j["size"];
if(j.contains("is_sized"))
probe.is_sized = j["is_sized"];
if(j.contains("intensity"))
probe.intensity = j["intensity"];
}
Object load_object(Scene& scene, const nlohmann::json obj) {
Object o = scene.add_object();
auto& data = scene.get(o);
data.name = obj["name"];
load_transform_component(obj["transform"], scene.get<Transform>(o));
if(obj.contains("renderable"))
load_renderable_component(obj["renderable"], scene.add<Renderable>(o));
if(obj.contains("light"))
load_light_component(obj["light"], scene.add<Light>(o));
if(obj.contains("camera"))
load_camera_component(obj["camera"], scene.add<Camera>(o));
if(obj.contains("collision"))
load_collision_component(obj["collision"], scene.add<Collision>(o));
if(obj.contains("rigidbody"))
load_rigidbody_component(obj["rigidbody"], scene.add<Rigidbody>(o));
if(obj.contains("ui"))
load_ui_component(obj["ui"], scene.add<UI>(o));
if(obj.contains("environment_probe"))
load_probe_component(obj["environment_probe"], scene.add<EnvironmentProbe>(o));
return o;
}
void save_transform_component(nlohmann::json& j, const Transform& t) {
j["position"] = t.position;
j["scale"] = t.scale;
j["rotation"] = t.rotation;
}
void save_renderable_component(nlohmann::json& j, const Renderable &mesh) {
if(mesh.mesh)
j["path"] = mesh.mesh->path;
for(auto& material : mesh.materials) {
if(material)
j["materials"].push_back(material->path);
}
}
void save_camera_component(nlohmann::json& j, const Camera& camera) {
j["fov"] = camera.fov;
}
void save_light_component(nlohmann::json& j, const Light& light) {
j["color"] = light.color;
j["power"] = light.power;
j["type"] = light.type;
j["size"] = light.size;
j["spot_size"] = light.spot_size;
j["enable_shadows"] = light.enable_shadows;
j["use_dynamic_shadows"] = light.use_dynamic_shadows;
}
void save_collision_component(nlohmann::json& j, const Collision& collision) {
j["type"] = collision.type;
j["size"] = collision.size;
j["is_trigger"] = collision.is_trigger;
if(collision.is_trigger)
j["trigger_id"] = collision.trigger_id;
}
void save_rigidbody_component(nlohmann::json& j, const Rigidbody& rigidbody) {
j["type"] = rigidbody.type;
j["mass"] = rigidbody.mass;
}
void save_ui_component(nlohmann::json& j, const UI& ui) {
j["width"] = ui.width;
j["height"] = ui.height;
j["path"] = ui.ui_path;
}
void save_probe_component(nlohmann::json& j, const EnvironmentProbe& probe) {
j["size"] = probe.size;
j["is_sized"] = probe.is_sized;
j["intensity"] = probe.intensity;
}
nlohmann::json save_object(Object obj) {
nlohmann::json j;
auto& data = engine->get_scene()->get(obj);
j["name"] = data.name;
if(data.parent != NullObject)
j["parent"] = engine->get_scene()->get(data.parent).name;
save_transform_component(j["transform"], engine->get_scene()->get<Transform>(obj));
auto scene = engine->get_scene();
if(scene->has<Renderable>(obj))
save_renderable_component(j["renderable"], scene->get<Renderable>(obj));
if(scene->has<Light>(obj))
save_light_component(j["light"], scene->get<Light>(obj));
if(scene->has<Camera>(obj))
save_camera_component(j["camera"], scene->get<Camera>(obj));
if(scene->has<Collision>(obj))
save_collision_component(j["collision"], scene->get<Collision>(obj));
if(scene->has<Rigidbody>(obj))
save_rigidbody_component(j["rigidbody"], scene->get<Rigidbody>(obj));
if(scene->has<UI>(obj))
save_ui_component(j["ui"], scene->get<UI>(obj));
if(scene->has<EnvironmentProbe>(obj))
save_probe_component(j["environment_probe"], scene->get<EnvironmentProbe>(obj));
return j;
}

220
engine/core/src/screen.cpp Executable file
View file

@ -0,0 +1,220 @@
#include "screen.hpp"
#include <fstream>
#include <string_view>
#include <nlohmann/json.hpp>
#include "file.hpp"
#include "font.hpp"
#include "engine.hpp"
#include "string_utils.hpp"
#include "log.hpp"
#include "assertions.hpp"
void ui::Screen::calculate_sizes() {
unsigned int numChildren = 0;
int actualWidth = extent.width, actualHeight = extent.height;
int offsetX = 0, offsetY = 0;
for(size_t i = 0; i < elements.size(); i++) {
auto& element = elements[i];
auto id = "ui_" + element.id;
//if(engine->has_localization(id))
// element.text = engine->localize(id);
UIElement* parent = nullptr;
if(!element.parent.empty())
parent = find_element(element.parent);
const auto& fillAbsolute = [parent](const auto& metric, auto& absolute, auto base, auto offset) {
switch(metric.type) {
case MetricType::Absolute:
absolute = offset + metric.value;
break;
case MetricType::Relative:
absolute = offset + ((base - offset) * (metric.value / 100.0f));
break;
case MetricType::Offset:
{
if(parent == nullptr)
absolute = base - metric.value;
else
absolute = parent->absolute_height - metric.value;
}
break;
}
};
fillAbsolute(element.metrics.x, element.absolute_x, offsetX + actualWidth, offsetX);
fillAbsolute(element.metrics.y, element.absolute_y, offsetY + actualHeight, offsetY);
fillAbsolute(element.metrics.width, element.absolute_width, actualWidth, 0);
fillAbsolute(element.metrics.height, element.absolute_height, actualHeight, 0);
if(parent) {
const float heightPerRow = actualWidth / (float)elements.size();
element.absolute_x = (float)numChildren * heightPerRow;
element.absolute_y = actualHeight - 50.0f;
element.absolute_width = (float)heightPerRow;
element.absolute_height = (float)50.0f;
numChildren++;
}
if(element.text_location == UIElement::TextLocation::Center) {
if(get_string_width(element.text) < element.absolute_width)
element.text_x = (element.absolute_width / 2.0f) - (get_string_width(element.text) / 2.0f);
if(get_font_height() < element.absolute_height)
element.text_y = (element.absolute_height / 2.0f) - (get_font_height() / 2.0f);
}
}
}
void ui::Screen::process_mouse(const int x, const int y) {
Expects(x >= 0);
Expects(y >= 0);
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
for(auto& element : elements) {
if(x > element.absolute_x &&
y > element.absolute_y &&
x < (element.absolute_x + element.absolute_width) &&
y < (element.absolute_y + element.absolute_height)) {
if(!element.on_click_script.empty())
engine->lua.safe_script(element.on_click_script, env);
}
}
#endif
}
UIElement* ui::Screen::find_element(const std::string& id) {
Expects(!id.empty());
UIElement* foundElement = nullptr;
for(auto& element : elements) {
if(element.id == id)
foundElement = &element;
}
Expects(foundElement != nullptr);
return foundElement;
}
ui::Screen::Screen(const file::Path path) {
auto file = file::open(path);
if(!file.has_value()) {
console::error(System::Core, "Failed to load UI from {}!", path);
return;
}
nlohmann::json j;
file->read_as_stream() >> j;
if(j.count("script"))
script_path = j["script"];
for(auto& element : j["elements"]) {
UIElement ue;
ue.id = element["id"];
if(element.contains("on_click"))
ue.on_click_script = element["on_click"];
if(element.contains("text"))
ue.text = element["text"];
if(element.contains("parent"))
ue.parent = element["parent"];
if(element.contains("metrics")) {
const auto& metrics = element["metrics"];
const auto parseMetric = [](const std::string& str) {
UIElement::Metrics::Metric m;
if(string_contains(str, "px")) {
auto v = remove_substring(str, "px");
if(string_contains(str, "@")) {
m.type = MetricType::Offset;
m.value = std::stoi(remove_substring(v, "@"));
} else {
m.type = MetricType::Absolute;
m.value = std::stoi(v);
}
} else if(string_contains(str, "%")) {
m.type = MetricType::Relative;
m.value = std::stoi(remove_substring(str, "%"));
}
return m;
};
if(metrics.contains("x"))
ue.metrics.x = parseMetric(metrics["x"]);
if(metrics.contains("y"))
ue.metrics.y = parseMetric(metrics["y"]);
if(metrics.contains("width"))
ue.metrics.width = parseMetric(metrics["width"]);
if(metrics.contains("height"))
ue.metrics.height = parseMetric(metrics["height"]);
}
if(element.contains("background")) {
if(element["background"].contains("color")) {
auto tokens = tokenize(element["background"]["color"].get<std::string_view>(), ",");
ue.background.color.r = std::stof(tokens[0]);
ue.background.color.g = std::stof(tokens[1]);
ue.background.color.b = std::stof(tokens[2]);
ue.background.color.a = std::stof(tokens[3]);
}
if(element["background"].contains("image")) {
ue.background.image = element["background"]["image"];
}
}
if(element.contains("wrap"))
ue.wrap_text = element["wrap"];
if(element.contains("textLocation")) {
if(element["textLocation"] == "topLeft")
ue.text_location = UIElement::TextLocation::TopLeft;
else if(element["textLocation"] == "center")
ue.text_location = UIElement::TextLocation::Center;
}
elements.push_back(ue);
}
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
env = sol::environment(engine->lua, sol::create, engine->lua.globals());
env["screen"] = this;
if(!script_path.empty()) {
auto script_file = file::open(file::app_domain / script_path);
if(script_file != std::nullopt)
engine->lua.safe_script(script_file->read_as_string(), env);
}
#endif
}
void ui::Screen::add_listener(const std::string& id, std::function<void()> callback) {
listeners[id] = callback;
}
void ui::Screen::process_event(const std::string& type, const std::string data) {
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
auto func = env["process_event"];
func(type, data);
#endif
}

24
engine/gfx/CMakeLists.txt Executable file
View file

@ -0,0 +1,24 @@
set(CMAKE_FOLDER "GFX Backends")
add_library(GFX INTERFACE)
target_include_directories(GFX INTERFACE public)
add_custom_target(GFXInterface SOURCES
public/gfx.hpp
public/gfx_buffer.hpp
public/gfx_pipeline.hpp
public/gfx_commandbuffer.hpp
public/gfx_texture.hpp
public/gfx_framebuffer.hpp
public/gfx_renderpass.hpp
public/gfx_object.hpp
public/gfx_sampler.hpp)
set_target_properties(GFXInterface PROPERTIES CMAKE_FOLDER "GFX")
if(ENABLE_METAL)
add_subdirectory(metal)
endif()
if(ENABLE_VULKAN)
add_subdirectory(vulkan)
endif()

View file

@ -0,0 +1,3 @@
add_library(GFXDummy STATIC src/gfx_dummy.cpp)
target_include_directories(GFXDummy PUBLIC include)
target_link_libraries(GFXDummy PUBLIC GFX)

View file

@ -0,0 +1,30 @@
#pragma once
#include "gfx.hpp"
class GFXDummy : public GFX {
public:
bool initialize() override;
void initializeView(void* native_handle, uint32_t width, uint32_t height) override;
// buffer operations
GFXBuffer* createBuffer(void* data, GFXSize size, GFXBufferUsage usage) override;
void copyBuffer(GFXBuffer* buffer, void* data, GFXSize offset, GFXSize size) override;
// texture operations
GFXTexture* createTexture(uint32_t width, uint32_t height, GFXPixelFormat format, GFXStorageMode storageMode, GFXTextureUsage usage) override;
void copyTexture(GFXTexture* texture, void* data, GFXSize size) override;
// framebuffer operations
GFXFramebuffer* createFramebuffer(GFXFramebufferCreateInfo& info) override;
// render pass operations
GFXRenderPass* createRenderPass(GFXRenderPassCreateInfo& info) override;
// pipeline operations
GFXPipeline* createPipeline(GFXPipelineCreateInfo& info) override;
void render(GFXCommandBuffer* command_buffer) override;
const char* getName() override;
};

View file

@ -0,0 +1,46 @@
#include "gfx_dummy.hpp"
bool GFXDummy::initialize() {
return true;
}
void GFXDummy::initializeView(void* native_handle, uint32_t width, uint32_t height) {
}
GFXBuffer* GFXDummy::createBuffer(void* data, GFXSize size, GFXBufferUsage usage) {
return nullptr;
}
void GFXDummy::copyBuffer(GFXBuffer* buffer, void* data, GFXSize offset, GFXSize size) {
}
GFXTexture* GFXDummy::createTexture(uint32_t width, uint32_t height, GFXPixelFormat format, GFXStorageMode storageMode, GFXTextureUsage usage) {
return nullptr;
}
void GFXDummy::copyTexture(GFXTexture* texture, void* data, GFXSize size) {
}
GFXFramebuffer* GFXDummy::createFramebuffer(GFXFramebufferCreateInfo& info) {
return nullptr;
}
GFXRenderPass* GFXDummy::createRenderPass(GFXRenderPassCreateInfo& info) {
return nullptr;
}
GFXPipeline* GFXDummy::createPipeline(GFXPipelineCreateInfo& info) {
return nullptr;
}
void GFXDummy::render(GFXCommandBuffer* command_buffer) {
}
const char* GFXDummy::getName() {
return "None";
}

28
engine/gfx/metal/CMakeLists.txt Executable file
View file

@ -0,0 +1,28 @@
set(HEADERS
include/gfx_metal.hpp
src/gfx_metal_buffer.hpp
src/gfx_metal_pipeline.hpp
src/gfx_metal_texture.hpp
src/gfx_metal_framebuffer.hpp
src/gfx_metal_renderpass.hpp
src/gfx_metal_sampler.hpp)
add_library(GFXMetal STATIC
src/gfx_metal.mm
${HEADERS})
set_target_properties(GFXMetal PROPERTIES
FRAMEWORK TRUE
FRAMEWORK_VERSION OBJC
PUBLIC_HEADER "${HEADERS}"
)
target_link_libraries(GFXMetal PUBLIC
GFX
Core
Log
"-framework Metal")
target_include_directories(GFXMetal PUBLIC
include
PRIVATE
src)
set_engine_properties(GFXMetal)

View file

@ -0,0 +1,68 @@
#pragma once
#include <Metal/Metal.h>
#include <MetalKit/MetalKit.h>
#include "gfx.hpp"
class GFXMetal : public GFX {
public:
bool is_supported() override;
GFXContext required_context() override { return GFXContext::Metal; }
const char* get_name() override;
bool supports_feature(const GFXFeature feature) override;
bool initialize(const GFXCreateInfo& createInfo) override;
void initialize_view(void* native_handle, const int identifier, const uint32_t width, const uint32_t height) override;
void remove_view(const int identifier) override;
// buffer operations
GFXBuffer* create_buffer(void* data, const GFXSize size, const bool dynamicData, const GFXBufferUsage usage) override;
void copy_buffer(GFXBuffer* buffer, void* data, const GFXSize offset, const GFXSize size) override;
void* get_buffer_contents(GFXBuffer* buffer) override;
// texture operations
GFXTexture* create_texture(const GFXTextureCreateInfo& info) override;
void copy_texture(GFXTexture* texture, void* data, GFXSize size) override;
void copy_texture(GFXTexture* from, GFXTexture* to) override;
void copy_texture(GFXTexture* from, GFXBuffer* to) override;
// sampler opeations
GFXSampler* create_sampler(const GFXSamplerCreateInfo& info) override;
// framebuffer operations
GFXFramebuffer* create_framebuffer(const GFXFramebufferCreateInfo& info) override;
// render pass operations
GFXRenderPass* create_render_pass(const GFXRenderPassCreateInfo& info) override;
// pipeline operations
GFXPipeline* create_graphics_pipeline(const GFXGraphicsPipelineCreateInfo& info) override;
GFXCommandBuffer* acquire_command_buffer() override;
void submit(GFXCommandBuffer* command_buffer, const int window = -1) override;
private:
struct NativeMTLView {
int identifier = -1;
CAMetalLayer* layer = nullptr;
};
std::vector<NativeMTLView*> nativeViews;
NativeMTLView* getNativeView(int identifier) {
for(auto& view : nativeViews) {
if(view->identifier == identifier)
return view;
}
return nullptr;
}
id<MTLDevice> device = nil;
id<MTLCommandQueue> command_queue = nil;
};

999
engine/gfx/metal/src/gfx_metal.mm Executable file
View file

@ -0,0 +1,999 @@
#include "gfx_metal.hpp"
#include "gfx_metal_buffer.hpp"
#include "gfx_metal_pipeline.hpp"
#include "gfx_commandbuffer.hpp"
#include "gfx_metal_texture.hpp"
#include "gfx_metal_renderpass.hpp"
#include "gfx_metal_framebuffer.hpp"
#include "gfx_metal_sampler.hpp"
#include "file.hpp"
#include "log.hpp"
#include "utility.hpp"
#include "string_utils.hpp"
static inline bool debug_enabled = false;
static inline std::array<GFXCommandBuffer*, 15> command_buffers;
static inline std::array<bool, 15> free_command_buffers;
MTLPixelFormat toPixelFormat(GFXPixelFormat format) {
switch(format) {
case GFXPixelFormat::R_32F:
return MTLPixelFormatR32Float;
case GFXPixelFormat::RGBA_32F:
return MTLPixelFormatRGBA32Float;
case GFXPixelFormat::RGBA8_UNORM:
return MTLPixelFormatRGBA8Unorm;
case GFXPixelFormat::R8_UNORM:
return MTLPixelFormatR8Unorm;
case GFXPixelFormat::R8G8_UNORM:
return MTLPixelFormatRG8Unorm;
case GFXPixelFormat::R8G8_SFLOAT:
return MTLPixelFormatRG16Float;
case GFXPixelFormat::R8G8B8A8_UNORM:
return MTLPixelFormatRGBA8Unorm;
case GFXPixelFormat::R16G16B16A16_SFLOAT:
return MTLPixelFormatRGBA16Float;
case GFXPixelFormat::DEPTH_32F:
return MTLPixelFormatDepth32Float;
}
}
MTLBlendFactor toBlendFactor(GFXBlendFactor factor) {
switch(factor) {
case GFXBlendFactor::One:
return MTLBlendFactorOne;
case GFXBlendFactor::Zero:
return MTLBlendFactorZero;
case GFXBlendFactor::SrcColor:
return MTLBlendFactorSourceColor;
case GFXBlendFactor::DstColor:
return MTLBlendFactorDestinationColor;
case GFXBlendFactor::SrcAlpha:
return MTLBlendFactorSourceAlpha;
case GFXBlendFactor::DstAlpha:
return MTLBlendFactorDestinationAlpha;
case GFXBlendFactor::OneMinusSrcAlpha:
return MTLBlendFactorOneMinusSourceAlpha;
case GFXBlendFactor::OneMinusSrcColor:
return MTLBlendFactorOneMinusSourceColor;
}
}
MTLSamplerAddressMode toSamplingMode(SamplingMode mode) {
switch(mode) {
case SamplingMode::Repeat:
return MTLSamplerAddressModeRepeat;
case SamplingMode::ClampToEdge:
return MTLSamplerAddressModeClampToEdge;
case SamplingMode::ClampToBorder:
{
#if defined(PLATFORM_IOS) || defined(PLATFORM_TVOS)
return MTLSamplerAddressModeRepeat;
#else
return MTLSamplerAddressModeClampToBorderColor;
#endif
}
}
}
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
MTLSamplerBorderColor toBorderColor(GFXBorderColor color) {
switch(color) {
case GFXBorderColor::OpaqueWhite:
return MTLSamplerBorderColorOpaqueWhite;
case GFXBorderColor::OpaqueBlack:
return MTLSamplerBorderColorOpaqueBlack;
}
}
#endif
MTLCompareFunction toCompare(GFXCompareFunction function) {
switch(function) {
case GFXCompareFunction::Never:
return MTLCompareFunctionNever;
case GFXCompareFunction::Less:
return MTLCompareFunctionLess;
case GFXCompareFunction::Equal:
return MTLCompareFunctionEqual;
case GFXCompareFunction::LessOrEqual:
return MTLCompareFunctionLessEqual;
case GFXCompareFunction::Greater:
return MTLCompareFunctionGreater;
case GFXCompareFunction::NotEqual:
return MTLCompareFunctionNotEqual;
case GFXCompareFunction::GreaterOrEqual:
return MTLCompareFunctionGreaterEqual;
case GFXCompareFunction::Always:
return MTLCompareFunctionAlways;
}
}
MTLSamplerMinMagFilter toFilter(GFXFilter filter) {
switch(filter) {
case GFXFilter::Nearest:
return MTLSamplerMinMagFilterNearest;
case GFXFilter::Linear:
return MTLSamplerMinMagFilterLinear;
}
}
bool GFXMetal::is_supported() {
return true;
}
bool GFXMetal::initialize(const GFXCreateInfo& createInfo) {
debug_enabled = createInfo.api_validation_enabled;
device = MTLCreateSystemDefaultDevice();
if(device) {
command_queue = [device newCommandQueue];
for(int i = 0; i < 15; i++) {
command_buffers[i] = new GFXCommandBuffer();
free_command_buffers[i] = true;
}
return true;
} else {
return false;
}
}
const char* GFXMetal::get_name() {
return "Metal";
}
bool GFXMetal::supports_feature(const GFXFeature feature) {
if(feature == GFXFeature::CubemapArray) {
#if defined(PLATFORM_TVOS)
return false;
#else
return true;
#endif
}
return false;
}
void GFXMetal::initialize_view(void* native_handle, const int identifier, const uint32_t, const uint32_t) {
NativeMTLView* native = new NativeMTLView();
native->identifier = identifier;
native->layer = (CAMetalLayer*)native_handle;
native->layer.device = device;
native->layer.allowsNextDrawableTimeout = true;
nativeViews.push_back(native);
}
void GFXMetal::remove_view(const int identifier) {
utility::erase_if(nativeViews, [identifier](NativeMTLView* view) {
return view->identifier == identifier;
});
}
GFXBuffer* GFXMetal::create_buffer(void* data, const GFXSize size, const bool dynamicData, const GFXBufferUsage) {
GFXMetalBuffer* buffer = new GFXMetalBuffer();
buffer->dynamicData = dynamicData;
if(buffer->dynamicData) {
for(int i = 0; i < 3; i++) {
if(data == nullptr) {
buffer->handles[i] = [device newBufferWithLength:(NSUInteger)size options:MTLResourceStorageModeShared];
} else {
buffer->handles[i] = [device newBufferWithBytes:data
length:(NSUInteger)size
options:MTLResourceStorageModeShared];
}
}
} else {
if(data == nullptr) {
buffer->handles[0] = [device newBufferWithLength:(NSUInteger)size options:MTLResourceStorageModeShared];
} else {
buffer->handles[0] = [device newBufferWithBytes:data
length:(NSUInteger)size
options:MTLResourceStorageModeShared];
}
}
return buffer;
}
int currentFrameIndex = 0;
void GFXMetal::copy_buffer(GFXBuffer* buffer, void* data, const GFXSize offset, const GFXSize size) {
GFXMetalBuffer* metalBuffer = (GFXMetalBuffer*)buffer;
const unsigned char * src = reinterpret_cast<const unsigned char*>(data);
unsigned char * dest = reinterpret_cast<unsigned char *>(metalBuffer->get(currentFrameIndex).contents);
memcpy(dest + offset, src, size);
//[metalBuffer->handle didModifyRange:NSMakeRange(offset, size)];
}
void* GFXMetal::get_buffer_contents(GFXBuffer* buffer) {
GFXMetalBuffer* metalBuffer = (GFXMetalBuffer*)buffer;
return reinterpret_cast<unsigned char *>(metalBuffer->get(currentFrameIndex).contents);
}
GFXTexture* GFXMetal::create_texture(const GFXTextureCreateInfo& info) {
GFXMetalTexture* texture = new GFXMetalTexture();
MTLTextureDescriptor* textureDescriptor = [[MTLTextureDescriptor alloc] init];
MTLPixelFormat mtlFormat = toPixelFormat(info.format);
switch(info.type) {
case GFXTextureType::Single2D:
textureDescriptor.textureType = MTLTextureType2D;
break;
case GFXTextureType::Array2D:
textureDescriptor.textureType = MTLTextureType2DArray;
break;
case GFXTextureType::Cubemap:
{
textureDescriptor.textureType = MTLTextureTypeCube;
texture->is_cubemap = true;
}
break;
case GFXTextureType::CubemapArray:
{
textureDescriptor.textureType = MTLTextureTypeCubeArray;
texture->is_cubemap = true;
}
break;
}
if((info.usage & GFXTextureUsage::Attachment) == GFXTextureUsage::Attachment) {
textureDescriptor.storageMode = MTLStorageModePrivate;
textureDescriptor.usage |= MTLTextureUsageRenderTarget;
} else {
#if defined(PLATFORM_IOS) || defined(PLATFORM_TVOS)
textureDescriptor.storageMode = MTLStorageModeShared;
#else
textureDescriptor.storageMode = MTLStorageModeManaged;
#endif
}
if((info.usage & GFXTextureUsage::Sampled) == GFXTextureUsage::Sampled) {
textureDescriptor.usage |= MTLTextureUsageShaderRead;
}
textureDescriptor.pixelFormat = mtlFormat;
textureDescriptor.width = info.width;
textureDescriptor.height = info.height;
textureDescriptor.arrayLength = info.array_length;
texture->array_length = info.array_length;
textureDescriptor.mipmapLevelCount = info.mip_count;
texture->format = mtlFormat;
texture->handle = [device newTextureWithDescriptor:textureDescriptor];
texture->width = info.width;
texture->height = info.height;
MTLSamplerDescriptor* samplerDescriptor = [MTLSamplerDescriptor new];
samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
samplerDescriptor.sAddressMode = toSamplingMode(info.samplingMode);
samplerDescriptor.tAddressMode = toSamplingMode(info.samplingMode);
samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear;
samplerDescriptor.maxAnisotropy = 16;
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
samplerDescriptor.borderColor = toBorderColor(info.border_color);
#endif
if(info.compare_enabled)
samplerDescriptor.compareFunction = toCompare(info.compare_function);
texture->sampler = [device newSamplerStateWithDescriptor:samplerDescriptor];
return texture;
}
void GFXMetal::copy_texture(GFXTexture* texture, void* data, const GFXSize) {
GFXMetalTexture* metalTexture = (GFXMetalTexture*)texture;
MTLRegion region = {
{ 0, 0, 0 },
{
static_cast<NSUInteger>(texture->width),
static_cast<NSUInteger>(texture->height),
1
}
};
NSUInteger byteSize = 1;
if(metalTexture->format == MTLPixelFormatRGBA8Unorm)
byteSize = 4;
else if(metalTexture->format == MTLPixelFormatRG8Unorm)
byteSize = 2;
[metalTexture->handle replaceRegion:region mipmapLevel:0 withBytes:data bytesPerRow:(NSUInteger)texture->width * byteSize];
}
void GFXMetal::copy_texture(GFXTexture* from, GFXTexture* to) {
GFXMetalTexture* metalFromTexture = (GFXMetalTexture*)from;
GFXMetalTexture* metalToTexture = (GFXMetalTexture*)to;
id<MTLCommandBuffer> commandBuffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> commandEncoder = [commandBuffer blitCommandEncoder];
[commandEncoder copyFromTexture:metalFromTexture->handle toTexture:metalToTexture->handle];
[commandEncoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
}
void GFXMetal::copy_texture(GFXTexture* from, GFXBuffer* to) {
GFXMetalTexture* metalFromTexture = (GFXMetalTexture*)from;
GFXMetalBuffer* metalToBuffer = (GFXMetalBuffer*)to;
MTLOrigin origin;
origin.x = 0;
origin.y = 0;
origin.z = 0;
MTLSize size;
size.width = from->width;
size.height = from->height;
size.depth = 1;
NSUInteger byteSize = 1;
if(metalFromTexture->format == MTLPixelFormatRGBA8Unorm)
byteSize = 4;
id<MTLCommandBuffer> commandBuffer = [command_queue commandBuffer];
id<MTLBlitCommandEncoder> commandEncoder = [commandBuffer blitCommandEncoder];
[commandEncoder copyFromTexture:metalFromTexture->handle
sourceSlice:0
sourceLevel:0
sourceOrigin:origin
sourceSize:size
toBuffer:metalToBuffer->get(currentFrameIndex)
destinationOffset:0
destinationBytesPerRow:(NSUInteger)metalFromTexture->width * byteSize
destinationBytesPerImage:0];
[commandEncoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
}
GFXSampler* GFXMetal::create_sampler(const GFXSamplerCreateInfo& info) {
GFXMetalSampler* sampler = new GFXMetalSampler();
MTLSamplerDescriptor* samplerDescriptor = [MTLSamplerDescriptor new];
samplerDescriptor.minFilter = toFilter(info.min_filter);
samplerDescriptor.magFilter = toFilter(info.mag_filter);
samplerDescriptor.sAddressMode = toSamplingMode(info.samplingMode);
samplerDescriptor.tAddressMode = toSamplingMode(info.samplingMode);
samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear;
samplerDescriptor.maxAnisotropy = 16;
#if !defined(PLATFORM_IOS) && !defined(PLATFORM_TVOS)
samplerDescriptor.borderColor = toBorderColor(info.border_color);
#endif
if(info.compare_enabled)
samplerDescriptor.compareFunction = toCompare(info.compare_function);
sampler->handle = [device newSamplerStateWithDescriptor:samplerDescriptor];
return sampler;
}
GFXFramebuffer* GFXMetal::create_framebuffer(const GFXFramebufferCreateInfo& info) {
GFXMetalFramebuffer* framebuffer = new GFXMetalFramebuffer();
for(auto& attachment : info.attachments)
framebuffer->attachments.push_back((GFXMetalTexture*)attachment);
return framebuffer;
}
GFXRenderPass* GFXMetal::create_render_pass(const GFXRenderPassCreateInfo& info) {
GFXMetalRenderPass* renderPass = new GFXMetalRenderPass();
for(const auto& attachment : info.attachments)
renderPass->attachments.push_back(toPixelFormat(attachment));
return renderPass;
}
MTLFunctionConstantValues* get_constant_values(GFXShaderConstants constants) {
MTLFunctionConstantValues* constantValues = [MTLFunctionConstantValues new];
for(auto& constant : constants) {
switch(constant.type) {
case GFXShaderConstant::Type::Integer:
[constantValues setConstantValue:&constant.value type:MTLDataTypeInt atIndex:constant.index];
break;
}
}
return constantValues;
}
GFXPipeline* GFXMetal::create_graphics_pipeline(const GFXGraphicsPipelineCreateInfo& info) {
GFXMetalPipeline* pipeline = new GFXMetalPipeline();
NSError* error = nil;
// vertex
id<MTLLibrary> vertexLibrary;
{
std::string vertex_src;
if(info.shaders.vertex_path.empty()) {
vertex_src = info.shaders.vertex_src;
} else {
const auto vertex_path = utility::format("{}.msl", info.shaders.vertex_path);
auto file = file::open(file::internal_domain / vertex_path);
if(file != std::nullopt) {
vertex_src = file->read_as_string();
} else {
console::error(System::GFX, "Failed to load vertex shader from {}!", vertex_path);
}
}
vertexLibrary = [device newLibraryWithSource:[NSString stringWithFormat:@"%s", vertex_src.c_str()] options:nil error:&error];
if(!vertexLibrary)
NSLog(@"%@", error.debugDescription);
}
// fragment
id<MTLLibrary> fragmentLibrary;
{
std::string fragment_src;
if(info.shaders.fragment_path.empty()) {
fragment_src = info.shaders.fragment_src;
} else {
const auto fragment_path = utility::format("{}.msl", info.shaders.fragment_path);
auto file = file::open(file::internal_domain / fragment_path);
if(file != std::nullopt) {
fragment_src = file->read_as_string();
} else {
console::error(System::GFX, "Failed to load fragment shader from {}!", fragment_path);
}
}
fragmentLibrary = [device newLibraryWithSource:[NSString stringWithFormat:@"%s", fragment_src.c_str()] options:nil error:&error];
if(!fragmentLibrary)
NSLog(@"%@", error.debugDescription);
}
auto vertex_constants = get_constant_values(info.shaders.vertex_constants);
id<MTLFunction> vertexFunc = [vertexLibrary newFunctionWithName:@"main0" constantValues:vertex_constants error:nil];
auto fragment_constants = get_constant_values(info.shaders.fragment_constants);
id<MTLFunction> fragmentFunc = [fragmentLibrary newFunctionWithName:@"main0" constantValues:fragment_constants error:nil];
if(debug_enabled) {
vertexFunc.label = [NSString stringWithFormat:@"%s", info.shaders.vertex_path.data()];
fragmentFunc.label = [NSString stringWithFormat:@"%s", info.shaders.fragment_path.data()];
}
MTLVertexDescriptor* descriptor = [MTLVertexDescriptor new];
for(auto input : info.vertex_input.inputs) {
descriptor.layouts[input.location].stride = (NSUInteger)input.stride;
descriptor.layouts[input.location].stepFunction = MTLVertexStepFunctionPerVertex;
GFXMetalPipeline::VertexStride vs;
vs.location = input.location;
vs.stride = input.stride;
pipeline->vertexStrides.push_back(vs);
}
for(auto attribute : info.vertex_input.attributes) {
NSUInteger format = MTLVertexFormatFloat3;
switch(attribute.format) {
case GFXVertexFormat::FLOAT2:
format = MTLVertexFormatFloat2;
break;
case GFXVertexFormat::FLOAT3:
format = MTLVertexFormatFloat3;
break;
case GFXVertexFormat::FLOAT4:
format = MTLVertexFormatFloat4;
break;
case GFXVertexFormat::INT:
format = MTLVertexFormatInt;
break;
case GFXVertexFormat::UNORM4:
format = MTLVertexFormatUChar4Normalized;
break;
case GFXVertexFormat::INT4:
format = MTLVertexFormatInt4;
break;
}
descriptor.attributes[attribute.location].format = (MTLVertexFormat)format;
descriptor.attributes[attribute.location].bufferIndex = (NSUInteger)attribute.binding;
descriptor.attributes[attribute.location].offset = (NSUInteger)attribute.offset;
}
MTLRenderPipelineDescriptor *pipelineDescriptor = [MTLRenderPipelineDescriptor new];
pipelineDescriptor.vertexFunction = vertexFunc;
pipelineDescriptor.fragmentFunction = fragmentFunc;
if(info.render_pass != nullptr) {
GFXMetalRenderPass* metalRenderPass = (GFXMetalRenderPass*)info.render_pass;
unsigned int i = 0;
for(const auto& attachment : metalRenderPass->attachments) {
if(attachment != MTLPixelFormatDepth32Float) {
pipelineDescriptor.colorAttachments[i].pixelFormat = attachment;
pipelineDescriptor.colorAttachments[i].blendingEnabled = info.blending.enable_blending;
pipelineDescriptor.colorAttachments[i].sourceRGBBlendFactor = toBlendFactor(info.blending.src_rgb);
pipelineDescriptor.colorAttachments[i].destinationRGBBlendFactor = toBlendFactor(info.blending.dst_rgb);
pipelineDescriptor.colorAttachments[i].sourceAlphaBlendFactor = toBlendFactor(info.blending.src_alpha);
pipelineDescriptor.colorAttachments[i].destinationAlphaBlendFactor = toBlendFactor(info.blending.dst_alpha);
i++;
} else {
pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float;
}
}
} else {
pipelineDescriptor.colorAttachments[0].pixelFormat = [nativeViews[0]->layer pixelFormat];
pipelineDescriptor.colorAttachments[0].blendingEnabled = info.blending.enable_blending;
pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = toBlendFactor(info.blending.src_rgb);
pipelineDescriptor.colorAttachments[0].destinationRGBBlendFactor = toBlendFactor(info.blending.dst_rgb);
pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = toBlendFactor(info.blending.src_alpha);
pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = toBlendFactor(info.blending.dst_alpha);
}
pipelineDescriptor.vertexDescriptor = descriptor;
if(debug_enabled) {
pipelineDescriptor.label = [NSString stringWithFormat:@"%s", info.label.data()];
pipeline->label = info.label;
}
pipeline->handle = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
if(!pipeline->handle)
NSLog(@"%@", error.debugDescription);
switch(info.rasterization.primitive_type) {
case GFXPrimitiveType::Triangle:
pipeline->primitiveType = MTLPrimitiveTypeTriangle;
break;
case GFXPrimitiveType::TriangleStrip:
pipeline->primitiveType = MTLPrimitiveTypeTriangleStrip;
break;
}
for(auto& binding : info.shader_input.bindings) {
if(binding.type == GFXBindingType::PushConstant)
pipeline->pushConstantIndex = binding.binding;
}
MTLDepthStencilDescriptor* depthStencil = [MTLDepthStencilDescriptor new];
if(info.depth.depth_mode != GFXDepthMode::None) {
//depthStencil.depthCompareFunction = info.depth.depth_mode == GFXDepthMode::LessOrEqual ? MTLCompareFunctionGreaterEqual : MTLCompareFunctionGreater;
depthStencil.depthCompareFunction = info.depth.depth_mode == GFXDepthMode::LessOrEqual ? MTLCompareFunctionLessEqual : MTLCompareFunctionLess;
depthStencil.depthWriteEnabled = true;
}
pipeline->depthStencil = [device newDepthStencilStateWithDescriptor:depthStencil];
switch(info.rasterization.culling_mode) {
case GFXCullingMode::Frontface:
pipeline->cullMode = MTLCullModeFront;
break;
case GFXCullingMode::Backface:
pipeline->cullMode = MTLCullModeBack;
break;
case GFXCullingMode::None:
pipeline->cullMode = MTLCullModeNone;
break;
}
if(info.rasterization.polygon_type == GFXPolygonType::Line)
pipeline->renderWire = true;
[vertexFunc release];
[fragmentFunc release];
[vertexLibrary release];
[fragmentLibrary release];
[vertex_constants release];
[fragment_constants release];
return pipeline;
}
GFXCommandBuffer* GFXMetal::acquire_command_buffer() {
for(const auto [i, buffer_status] : utility::enumerate(free_command_buffers)) {
if(buffer_status) {
GFXCommandBuffer* buffer = command_buffers[i];
free_command_buffers[i] = false;
buffer->commands.clear();
return buffer;
}
}
return nullptr;
}
void GFXMetal::submit(GFXCommandBuffer* command_buffer, const int window) {
@autoreleasepool {
NativeMTLView* native = getNativeView(window);
id<CAMetalDrawable> drawable = nil;
if(native != nullptr)
drawable = [native->layer nextDrawable];
id<MTLCommandBuffer> commandBuffer = [command_queue commandBuffer];
id <MTLRenderCommandEncoder> renderEncoder = nil;
id <MTLBlitCommandEncoder> blitEncoder = nil;
GFXMetalRenderPass* currentRenderPass = nullptr;
GFXMetalFramebuffer* currentFramebuffer = nullptr;
GFXMetalPipeline* currentPipeline = nullptr;
GFXMetalBuffer* currentIndexBuffer = nullptr;
IndexType currentIndextype = IndexType::UINT32;
MTLViewport currentViewport = MTLViewport();
MTLClearColor currentClearColor;
enum class CurrentEncoder {
None,
Render,
Blit
} current_encoder = CurrentEncoder::None;
const auto needEncoder = [&](CurrentEncoder encoder, bool needs_reset = false) {
if(encoder != current_encoder || needs_reset) {
if(renderEncoder != nil)
[renderEncoder endEncoding];
if(blitEncoder != nil)
[blitEncoder endEncoding];
renderEncoder = nil;
blitEncoder = nil;
}
if(current_encoder == encoder && !needs_reset)
return;
switch(encoder) {
case CurrentEncoder::None:
break;
case CurrentEncoder::Render:
{
MTLRenderPassDescriptor* descriptor = [MTLRenderPassDescriptor new];
if(currentRenderPass != nullptr && currentFramebuffer != nullptr) {
unsigned int i = 0;
for(const auto& attachment : currentFramebuffer->attachments) {
if(attachment->format == MTLPixelFormatDepth32Float) {
descriptor.depthAttachment.texture = attachment->handle;
descriptor.depthAttachment.loadAction = MTLLoadActionClear;
descriptor.depthAttachment.storeAction = MTLStoreActionStore;
} else {
descriptor.colorAttachments[i].texture = attachment->handle;
descriptor.colorAttachments[i].loadAction = MTLLoadActionClear;
descriptor.colorAttachments[i].storeAction = MTLStoreActionStore;
descriptor.colorAttachments[i].clearColor = currentClearColor;
i++;
}
}
renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:descriptor];
} else {
descriptor.colorAttachments[0].texture = drawable.texture;
descriptor.colorAttachments[0].loadAction = MTLLoadActionClear;
descriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0,0.0,0.0,1.0);
renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:descriptor];
}
if(currentViewport.width != 0.0f && currentViewport.height != 0.0f)
[renderEncoder setViewport:currentViewport];
[descriptor release];
}
break;
case CurrentEncoder::Blit:
{
blitEncoder = [commandBuffer blitCommandEncoder];
}
break;
}
current_encoder = encoder;
};
for(auto command : command_buffer->commands) {
switch(command.type) {
case GFXCommandType::Invalid:
break;
case GFXCommandType::SetRenderPass:
{
currentClearColor = MTLClearColorMake(command.data.set_render_pass.clear_color.r,
command.data.set_render_pass.clear_color.g,
command.data.set_render_pass.clear_color.b,
command.data.set_render_pass.clear_color.a );
currentFramebuffer = (GFXMetalFramebuffer*)command.data.set_render_pass.framebuffer;
currentRenderPass = (GFXMetalRenderPass*)command.data.set_render_pass.render_pass;
currentViewport = MTLViewport();
needEncoder(CurrentEncoder::Render, true);
}
break;
case GFXCommandType::SetPipeline:
{
needEncoder(CurrentEncoder::Render);
[renderEncoder setRenderPipelineState:((GFXMetalPipeline*)command.data.set_pipeline.pipeline)->handle];
currentPipeline = (GFXMetalPipeline*)command.data.set_pipeline.pipeline;
[renderEncoder setDepthStencilState:currentPipeline->depthStencil];
[renderEncoder setCullMode:((GFXMetalPipeline*)command.data.set_pipeline.pipeline)->cullMode];
if(currentPipeline->renderWire)
[renderEncoder setTriangleFillMode:MTLTriangleFillModeLines];
else
[renderEncoder setTriangleFillMode:MTLTriangleFillModeFill];
}
break;
case GFXCommandType::SetVertexBuffer:
{
needEncoder(CurrentEncoder::Render);
[renderEncoder setVertexBuffer:((GFXMetalBuffer*)command.data.set_vertex_buffer.buffer)->get(currentFrameIndex) offset:(NSUInteger)command.data.set_vertex_buffer.offset atIndex:(NSUInteger)command.data.set_vertex_buffer.index ];
}
break;
case GFXCommandType::SetIndexBuffer:
{
currentIndexBuffer = (GFXMetalBuffer*)command.data.set_index_buffer.buffer;
currentIndextype = command.data.set_index_buffer.index_type;
}
break;
case GFXCommandType::SetPushConstant:
{
needEncoder(CurrentEncoder::Render);
if(currentPipeline == nullptr)
continue;
[renderEncoder setVertexBytes:command.data.set_push_constant.bytes.data() length:(NSUInteger)command.data.set_push_constant.size atIndex:(NSUInteger)currentPipeline->pushConstantIndex];
[renderEncoder setFragmentBytes:command.data.set_push_constant.bytes.data() length:(NSUInteger)command.data.set_push_constant.size atIndex:(NSUInteger)currentPipeline->pushConstantIndex];
}
break;
case GFXCommandType::BindShaderBuffer:
{
needEncoder(CurrentEncoder::Render);
[renderEncoder setVertexBuffer:((GFXMetalBuffer*)command.data.bind_shader_buffer.buffer)->get(currentFrameIndex) offset:(NSUInteger)command.data.bind_shader_buffer.offset atIndex:(NSUInteger)command.data.bind_shader_buffer.index ];
[renderEncoder setFragmentBuffer:((GFXMetalBuffer*)command.data.bind_shader_buffer.buffer)->get(currentFrameIndex) offset:(NSUInteger)command.data.bind_shader_buffer.offset atIndex:(NSUInteger)command.data.bind_shader_buffer.index ];
}
break;
case GFXCommandType::BindTexture:
{
needEncoder(CurrentEncoder::Render);
if(command.data.bind_texture.texture != nullptr) {
[renderEncoder setVertexSamplerState:((GFXMetalTexture*)command.data.bind_texture.texture)->sampler atIndex:(NSUInteger)command.data.bind_texture.index];
[renderEncoder setVertexTexture:((GFXMetalTexture*)command.data.bind_texture.texture)->handle atIndex:(NSUInteger)command.data.bind_texture.index];
[renderEncoder setFragmentSamplerState:((GFXMetalTexture*)command.data.bind_texture.texture)->sampler atIndex:(NSUInteger)command.data.bind_texture.index];
[renderEncoder setFragmentTexture:((GFXMetalTexture*)command.data.bind_texture.texture)->handle atIndex:(NSUInteger)command.data.bind_texture.index];
} else {
[renderEncoder setVertexTexture:nil atIndex:(NSUInteger)command.data.bind_texture.index];
[renderEncoder setFragmentTexture:nil atIndex:(NSUInteger)command.data.bind_texture.index];
}
}
break;
case GFXCommandType::BindSampler:
{
needEncoder(CurrentEncoder::Render);
if(command.data.bind_sampler.sampler != nullptr) {
[renderEncoder setFragmentSamplerState:((GFXMetalSampler*)command.data.bind_sampler.sampler)->handle atIndex:(NSUInteger)command.data.bind_sampler.index];
} else {
[renderEncoder setFragmentSamplerState:nil atIndex:(NSUInteger)command.data.bind_sampler.index];
}
}
break;
case GFXCommandType::Draw:
{
needEncoder(CurrentEncoder::Render);
if(currentPipeline == nullptr)
continue;
[renderEncoder drawPrimitives:currentPipeline->primitiveType vertexStart:(NSUInteger)command.data.draw.vertex_offset vertexCount:(NSUInteger)command.data.draw.vertex_count instanceCount:(NSUInteger)command.data.draw.instance_count
baseInstance:(NSUInteger)command.data.draw.base_instance];
}
break;
case GFXCommandType::DrawIndexed:
{
needEncoder(CurrentEncoder::Render);
if(currentIndexBuffer == nullptr)
continue;
if(currentPipeline == nullptr)
continue;
MTLIndexType indexType;
int indexSize;
switch(currentIndextype) {
case IndexType::UINT16:
{
indexType = MTLIndexTypeUInt16;
indexSize = sizeof(uint16_t);
}
break;
case IndexType::UINT32:
{
indexType = MTLIndexTypeUInt32;
indexSize = sizeof(uint32_t);
}
break;
}
for(auto& stride : currentPipeline->vertexStrides)
[renderEncoder setVertexBufferOffset:(NSUInteger)command.data.draw_indexed.vertex_offset * stride.stride atIndex:stride.location];
[renderEncoder
drawIndexedPrimitives:currentPipeline->primitiveType
indexCount:(NSUInteger)command.data.draw_indexed.index_count
indexType:indexType
indexBuffer:currentIndexBuffer->get(currentFrameIndex)
indexBufferOffset:(NSUInteger)command.data.draw_indexed.first_index * indexSize];
}
break;
case GFXCommandType::MemoryBarrier:
{
needEncoder(CurrentEncoder::Render);
#ifdef PLATFORM_MACOS
[renderEncoder memoryBarrierWithScope:MTLBarrierScopeTextures afterStages:MTLRenderStageFragment beforeStages:MTLRenderStageFragment];
#endif
}
break;
case GFXCommandType::CopyTexture:
{
needEncoder(CurrentEncoder::Blit);
GFXMetalTexture* metalFromTexture = (GFXMetalTexture*)command.data.copy_texture.src;
GFXMetalTexture* metalToTexture = (GFXMetalTexture*)command.data.copy_texture.dst;
if(metalFromTexture != nullptr && metalToTexture != nullptr) {
const int slice_offset = command.data.copy_texture.to_slice + command.data.copy_texture.to_layer * 6;
[blitEncoder
copyFromTexture:metalFromTexture->handle
sourceSlice:0
sourceLevel:0
sourceOrigin:MTLOriginMake(0, 0, 0)
sourceSize:MTLSizeMake(command.data.copy_texture.width, command.data.copy_texture.height, 1)
toTexture:metalToTexture->handle
destinationSlice: slice_offset
destinationLevel:command.data.copy_texture.to_level
destinationOrigin: MTLOriginMake(0, 0, 0)];
}
}
break;
case GFXCommandType::SetViewport:
{
MTLViewport viewport;
viewport.originX = command.data.set_viewport.viewport.x;
viewport.originY = command.data.set_viewport.viewport.y;
viewport.width = command.data.set_viewport.viewport.width;
viewport.height = command.data.set_viewport.viewport.height;
viewport.znear = command.data.set_viewport.viewport.min_depth;
viewport.zfar = command.data.set_viewport.viewport.max_depth;
if(renderEncoder != nil)
[renderEncoder setViewport:viewport];
currentViewport = viewport;
}
break;
case GFXCommandType::SetScissor:
{
needEncoder(CurrentEncoder::Render);
MTLScissorRect rect;
rect.x = (NSUInteger)command.data.set_scissor.rect.offset.x;
rect.y = (NSUInteger)command.data.set_scissor.rect.offset.y;
rect.width = (NSUInteger)command.data.set_scissor.rect.extent.width;
rect.height = (NSUInteger)command.data.set_scissor.rect.extent.height;
[renderEncoder setScissorRect:rect];
}
break;
case GFXCommandType::GenerateMipmaps: {
needEncoder(CurrentEncoder::Blit);
GFXMetalTexture* metalTexture = (GFXMetalTexture*)command.data.generate_mipmaps.texture;
[blitEncoder generateMipmapsForTexture: metalTexture->handle];
}
break;
case GFXCommandType::SetDepthBias: {
needEncoder(CurrentEncoder::Render);
[renderEncoder setDepthBias:command.data.set_depth_bias.constant
slopeScale:command.data.set_depth_bias.slope_factor
clamp:command.data.set_depth_bias.clamp];
}
break;
case GFXCommandType::PushGroup: {
[commandBuffer pushDebugGroup:[NSString stringWithUTF8String:command.data.push_group.name.data()]];
}
break;
case GFXCommandType::PopGroup: {
[commandBuffer popDebugGroup];
}
break;
case GFXCommandType::InsertLabel: {
switch(current_encoder) {
case CurrentEncoder::Render:
[renderEncoder insertDebugSignpost:[NSString stringWithUTF8String:command.data.insert_label.name.data()]];
break;
case CurrentEncoder::Blit:
[blitEncoder insertDebugSignpost:[NSString stringWithUTF8String:command.data.insert_label.name.data()]];
break;
default:
break;
}
}
break;
}
}
if(renderEncoder != nil)
[renderEncoder endEncoding];
if(blitEncoder != nil)
[blitEncoder endEncoding];
for(auto [i, buffer] : utility::enumerate(command_buffers)) {
if(buffer == command_buffer)
free_command_buffers[i] = true;
}
if(window != -1) {
[commandBuffer presentDrawable:drawable];
[commandBuffer commit];
currentFrameIndex = (currentFrameIndex + 1) % 3;
} else {
[commandBuffer commit];
}
}
}

View file

@ -0,0 +1,19 @@
#pragma once
#include <Metal/Metal.h>
#include "gfx_buffer.hpp"
class GFXMetalBuffer : public GFXBuffer {
public:
id<MTLBuffer> handles[3] = {nil, nil, nil};
bool dynamicData = false;
id<MTLBuffer> get(int frameIndex) {
if(dynamicData) {
return handles[frameIndex];
} else {
return handles[0];
}
}
};

View file

@ -0,0 +1,13 @@
#pragma once
#include <Metal/Metal.h>
#include <vector>
#include "gfx_framebuffer.hpp"
class GFXMetalTexture;
class GFXMetalFramebuffer : public GFXFramebuffer {
public:
std::vector<GFXMetalTexture*> attachments;
};

View file

@ -0,0 +1,27 @@
#pragma once
#include <Metal/Metal.h>
#include "gfx_pipeline.hpp"
class GFXMetalPipeline : public GFXPipeline {
public:
std::string label;
id<MTLRenderPipelineState> handle = nil;
id<MTLDepthStencilState> depthStencil = nil;
MTLPrimitiveType primitiveType;
MTLCullMode cullMode;
struct VertexStride {
int location, stride;
};
std::vector<VertexStride> vertexStrides;
int pushConstantSize = 0;
int pushConstantIndex = 0;
bool renderWire = false;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <Metal/Metal.h>
#include "gfx_renderpass.hpp"
class GFXMetalRenderPass : public GFXRenderPass {
public:
std::vector<MTLPixelFormat> attachments;
};

View file

@ -0,0 +1,10 @@
#pragma once
#include <Metal/Metal.h>
#include "gfx_sampler.hpp"
class GFXMetalSampler : public GFXSampler {
public:
id<MTLSamplerState> handle = nil;
};

View file

@ -0,0 +1,16 @@
#pragma once
#include <Metal/Metal.h>
#include "gfx_texture.hpp"
class GFXMetalTexture : public GFXTexture {
public:
id<MTLTexture> handle = nil;
id<MTLSamplerState> sampler = nil;
int array_length = 1;
bool is_cubemap = false;
MTLPixelFormat format;
};

332
engine/gfx/public/gfx.hpp Executable file
View file

@ -0,0 +1,332 @@
#pragma once
#include <string>
#include <vector>
class GFXBuffer;
class GFXPipeline;
class GFXCommandBuffer;
class GFXTexture;
class GFXFramebuffer;
class GFXRenderPass;
class GFXObject;
class GFXSampler;
enum class GFXPixelFormat : int {
R_32F = 0,
RGBA_32F = 1,
RGBA8_UNORM = 2,
R8_UNORM = 3,
R8G8_UNORM = 4,
R8G8_SFLOAT = 5,
R8G8B8A8_UNORM = 6,
R16G16B16A16_SFLOAT = 7,
DEPTH_32F = 8
};
enum class GFXVertexFormat : int {
FLOAT2 = 0,
FLOAT3 = 1,
FLOAT4 = 2,
INT = 3,
UNORM4 = 4,
INT4 = 5
};
enum class GFXTextureUsage : int {
Sampled = 1,
Attachment = 2
};
inline GFXTextureUsage operator|(const GFXTextureUsage a, const GFXTextureUsage b) {
return static_cast<GFXTextureUsage>(static_cast<int>(a) | static_cast<int>(b));
}
inline GFXTextureUsage operator&(const GFXTextureUsage a, const GFXTextureUsage b) {
return static_cast<GFXTextureUsage>(static_cast<int>(a) & static_cast<int>(b));
}
enum class GFXBufferUsage : int {
Storage,
Vertex,
Index
};
enum class GFXBlendFactor : int {
One,
Zero,
SrcColor,
DstColor,
SrcAlpha,
DstAlpha,
OneMinusSrcAlpha,
OneMinusSrcColor
};
enum class SamplingMode : int {
Repeat,
ClampToEdge,
ClampToBorder
};
enum class GFXPrimitiveType {
Triangle,
TriangleStrip
};
enum class GFXPolygonType {
Fill,
Line
};
enum class GFXCullingMode {
Backface,
Frontface,
None
};
struct GFXVertexInput {
int location = 0;
int stride = 0;
};
struct GFXVertexAttribute {
int binding = 0, location = 0, offset = 0;
GFXVertexFormat format = GFXVertexFormat::FLOAT4;
};
enum class GFXDepthMode {
None,
Less,
LessOrEqual
};
struct GFXPushConstant {
int size = 0, offset = 0;
};
enum class GFXBindingType {
StorageBuffer,
PushConstant,
Texture
};
enum class GFXTextureType {
Single2D,
Array2D,
Cubemap,
CubemapArray
};
struct GFXShaderBinding {
int binding = 0;
GFXBindingType type = GFXBindingType::StorageBuffer;
};
struct GFXShaderConstant {
int index = 0;
enum class Type {
Integer
} type;
union {
int value;
};
};
using GFXShaderConstants = std::vector<GFXShaderConstant>;
struct GFXGraphicsPipelineCreateInfo {
std::string label; // only used for debug
struct Shaders {
std::string_view vertex_path, fragment_path;
std::string vertex_src, fragment_src;
GFXShaderConstants vertex_constants, fragment_constants;
} shaders;
struct Rasterization {
GFXPrimitiveType primitive_type = GFXPrimitiveType::Triangle;
GFXPolygonType polygon_type = GFXPolygonType::Fill;
GFXCullingMode culling_mode = GFXCullingMode::None;
} rasterization;
struct Blending {
GFXBlendFactor src_rgb = GFXBlendFactor::One, dst_rgb = GFXBlendFactor::One;
GFXBlendFactor src_alpha = GFXBlendFactor::One, dst_alpha = GFXBlendFactor::One;
bool enable_blending = false;
} blending;
struct VertexInput {
std::vector<GFXVertexInput> inputs;
std::vector<GFXVertexAttribute> attributes;
} vertex_input;
struct ShaderBindings {
std::vector<GFXPushConstant> push_constants;
std::vector<GFXShaderBinding> bindings;
} shader_input;
struct Depth {
GFXDepthMode depth_mode = GFXDepthMode::None;
} depth;
GFXRenderPass* render_pass = nullptr;
};
struct GFXComputePipelineCreateInfo {
std::string label; // only used for debug
struct Shaders {
std::string_view compute_path;
} shaders;
};
struct GFXFramebufferCreateInfo {
GFXRenderPass* render_pass;
std::vector<GFXTexture*> attachments;
};
struct GFXRenderPassCreateInfo {
std::vector<GFXPixelFormat> attachments;
};
enum class GFXBorderColor {
OpaqueWhite,
OpaqueBlack
};
enum class GFXCompareFunction {
Never,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreaterOrEqual,
Always
};
enum class GFXFilter {
Nearest,
Linear
};
struct GFXTextureCreateInfo {
GFXTextureType type = GFXTextureType::Single2D;
uint32_t width = 0;
uint32_t height = 0;
GFXPixelFormat format;
GFXTextureUsage usage;
int array_length = 1;
int mip_count = 1;
// sampler
SamplingMode samplingMode = SamplingMode::Repeat;
GFXBorderColor border_color = GFXBorderColor::OpaqueWhite;
bool compare_enabled = false;
GFXCompareFunction compare_function = GFXCompareFunction::Never;
};
struct GFXSamplerCreateInfo {
GFXFilter min_filter = GFXFilter::Linear, mag_filter = GFXFilter::Linear;
SamplingMode samplingMode = SamplingMode::Repeat;
GFXBorderColor border_color = GFXBorderColor::OpaqueWhite;
bool compare_enabled = false;
GFXCompareFunction compare_function = GFXCompareFunction::Never;
};
using GFXSize = uint64_t;
enum class GFXContext {
None,
Metal,
OpenGL,
DirectX,
Vulkan
};
struct GFXCreateInfo {
bool api_validation_enabled = false;
};
enum class GFXFeature {
CubemapArray
};
class GFX {
public:
// check for runtime support
virtual bool is_supported() { return false; }
virtual GFXContext required_context() { return GFXContext::None; }
virtual const char* get_name() { return nullptr; }
virtual bool supports_feature([[maybe_unused]] const GFXFeature feature) { return true; }
// try to initialize
virtual bool initialize([[maybe_unused]] const GFXCreateInfo& createInfo) { return false; }
virtual void initialize_view([[maybe_unused]] void* native_handle,
[[maybe_unused]] const int identifier,
[[maybe_unused]] const uint32_t width,
[[maybe_unused]] const uint32_t height) {}
virtual void recreate_view([[maybe_unused]] const int identifier,
[[maybe_unused]] const uint32_t width,
[[maybe_unused]] const uint32_t height) {}
virtual void remove_view([[maybe_unused]] const int identifier) {}
// buffer operations
virtual GFXBuffer* create_buffer([[maybe_unused]] void* data,
[[maybe_unused]] const GFXSize size,
[[maybe_unused]] const bool is_dynamic,
[[maybe_unused]] const GFXBufferUsage usage) { return nullptr; }
virtual void copy_buffer([[maybe_unused]] GFXBuffer* buffer,
[[maybe_unused]] void* data,
[[maybe_unused]] const GFXSize offset,
[[maybe_unused]] const GFXSize size) { }
virtual void* get_buffer_contents([[maybe_unused]] GFXBuffer* buffer) { return nullptr; }
virtual void release_buffer_contents([[maybe_unused]] GFXBuffer* buffer,
[[maybe_unused]] void* handle) {}
// texture operations
virtual GFXTexture* create_texture([[maybe_unused]] const GFXTextureCreateInfo& info) { return nullptr; }
virtual void copy_texture([[maybe_unused]] GFXTexture* texture,
[[maybe_unused]] void* data,
[[maybe_unused]] const GFXSize size) {}
virtual void copy_texture([[maybe_unused]] GFXTexture* from,
[[maybe_unused]] GFXTexture* to) {}
virtual void copy_texture([[maybe_unused]] GFXTexture* from,
[[maybe_unused]] GFXBuffer* to) {}
// sampler opeations
virtual GFXSampler* create_sampler([[maybe_unused]] const GFXSamplerCreateInfo& info) { return nullptr; }
// framebuffer operations
virtual GFXFramebuffer* create_framebuffer([[maybe_unused]] const GFXFramebufferCreateInfo& info) { return nullptr; }
// render pass operations
virtual GFXRenderPass* create_render_pass([[maybe_unused]] const GFXRenderPassCreateInfo& info) { return nullptr; }
// pipeline operations
virtual GFXPipeline* create_graphics_pipeline([[maybe_unused]] const GFXGraphicsPipelineCreateInfo& info) { return nullptr; }
virtual GFXPipeline* create_compute_pipeline([[maybe_unused]] const GFXComputePipelineCreateInfo& info) { return nullptr; }
// misc operations
virtual GFXSize get_alignment(const GFXSize size) { return size; }
virtual GFXCommandBuffer* acquire_command_buffer() { return nullptr; }
virtual void submit([[maybe_unused]] GFXCommandBuffer* command_buffer,
[[maybe_unused]] const int window = -1) {}
};

View file

@ -0,0 +1,8 @@
#pragma once
#include "gfx_object.hpp"
class GFXBuffer : public GFXObject {
public:
};

View file

@ -0,0 +1,338 @@
#pragma once
#include <vector>
#include <cstring>
#include <array>
#include <string_view>
#include "common.hpp"
class GFXPipeline;
class GFXBuffer;
class GFXFramebuffer;
class GFXRenderPass;
class GFXSampler;
struct GFXRenderPassBeginInfo {
struct ClearColor {
float r = 0.0f, g = 0.0f, b = 0.0f, a = 0.0f;
} clear_color;
Rectangle render_area;
GFXRenderPass* render_pass = nullptr;
GFXFramebuffer* framebuffer = nullptr;
};
struct Viewport {
float x = 0.0f;
float y = 0.0f;
float width = 0.0f;
float height = 0.0f;
float min_depth = 0.0f;
float max_depth = 1.0f;
};
enum class IndexType : int {
UINT16 = 0,
UINT32 = 1
};
enum class GFXCommandType {
Invalid,
SetRenderPass,
SetPipeline,
SetVertexBuffer,
SetIndexBuffer,
SetPushConstant,
BindShaderBuffer,
BindTexture,
BindSampler,
Draw,
DrawIndexed,
MemoryBarrier,
CopyTexture,
SetViewport,
SetScissor,
GenerateMipmaps,
SetDepthBias,
PushGroup,
PopGroup,
InsertLabel
};
struct GFXDrawCommand {
GFXCommandType type = GFXCommandType::Invalid;
struct CommandData {
GFXRenderPassBeginInfo set_render_pass;
struct SetPipelineData {
SetPipelineData() {}
GFXPipeline* pipeline = nullptr;
} set_pipeline;
struct SetVertexData {
GFXBuffer* buffer = nullptr;
int offset = 0;
int index = 0;
} set_vertex_buffer;
struct SetIndexData {
GFXBuffer* buffer = nullptr;
IndexType index_type = IndexType::UINT32;
} set_index_buffer;
struct SetPushData {
std::vector<unsigned char> bytes;
size_t size = 0;
} set_push_constant;
struct BindShaderData {
GFXBuffer* buffer = nullptr;
int offset = 0;
int index = 0;
int size = 0;
} bind_shader_buffer;
struct BindTextureData {
GFXTexture* texture = nullptr;
int index = 0;
} bind_texture;
struct BindSamplerData {
GFXSampler* sampler = nullptr;
int index = 0;
} bind_sampler;
struct DrawData {
int vertex_offset = 0;
int vertex_count = 0;
int base_instance = 0;
int instance_count = 0;
} draw;
struct DrawIndexedData {
int index_count = 0;
int first_index = 0;
int vertex_offset = 0;
} draw_indexed;
struct CopyTextureData {
GFXTexture* src = nullptr, *dst = nullptr;
int width = 0, height = 0;
int to_slice = 0;
int to_layer = 0;
int to_level = 0;
} copy_texture;
struct SetViewportData {
Viewport viewport;
} set_viewport;
struct SetScissorData {
Rectangle rect;
} set_scissor;
struct GenerateMipmapData {
GFXTexture* texture = nullptr;
int mip_count = 0;
} generate_mipmaps;
struct SetDepthBiasData {
float constant = 0.0f;
float clamp = 0.0f;
float slope_factor = 0.0f;
} set_depth_bias;
struct PushGroupData {
std::string_view name;
} push_group;
struct InsertLabelData {
std::string_view name;
} insert_label;
} data;
};
class GFXCommandBuffer {
public:
void set_render_pass(GFXRenderPassBeginInfo& info) {
GFXDrawCommand command;
command.type = GFXCommandType::SetRenderPass;
command.data.set_render_pass = info;
commands.push_back(command);
}
void set_pipeline(GFXPipeline* pipeline) {
GFXDrawCommand command;
command.type = GFXCommandType::SetPipeline;
command.data.set_pipeline.pipeline = pipeline;
commands.push_back(command);
}
void set_vertex_buffer(GFXBuffer* buffer, int offset, int index) {
GFXDrawCommand command;
command.type = GFXCommandType::SetVertexBuffer;
command.data.set_vertex_buffer.buffer = buffer;
command.data.set_vertex_buffer.offset = offset;
command.data.set_vertex_buffer.index = index;
commands.push_back(command);
}
void set_index_buffer(GFXBuffer* buffer, IndexType indexType) {
GFXDrawCommand command;
command.type = GFXCommandType::SetIndexBuffer;
command.data.set_index_buffer.buffer = buffer;
command.data.set_index_buffer.index_type = indexType;
commands.push_back(command);
}
void set_push_constant(const void* data, const size_t size) {
GFXDrawCommand command;
command.type = GFXCommandType::SetPushConstant;
command.data.set_push_constant.size = size;
command.data.set_push_constant.bytes.resize(size);
memcpy(command.data.set_push_constant.bytes.data(), data, size);
commands.push_back(command);
}
void bind_shader_buffer(GFXBuffer* buffer, int offset, int index, int size) {
GFXDrawCommand command;
command.type = GFXCommandType::BindShaderBuffer;
command.data.bind_shader_buffer.buffer = buffer;
command.data.bind_shader_buffer.offset = offset;
command.data.bind_shader_buffer.index = index;
command.data.bind_shader_buffer.size = size;
commands.push_back(command);
}
void bind_texture(GFXTexture* texture, int index) {
GFXDrawCommand command;
command.type = GFXCommandType::BindTexture;
command.data.bind_texture.texture = texture;
command.data.bind_texture.index = index;
commands.push_back(command);
}
void bind_sampler(GFXSampler* sampler, const int index) {
GFXDrawCommand command;
command.type = GFXCommandType::BindSampler;
command.data.bind_sampler.sampler = sampler;
command.data.bind_sampler.index = index;
commands.push_back(command);
}
void draw(int offset, int count, int instance_base, int instance_count) {
GFXDrawCommand command;
command.type = GFXCommandType::Draw;
command.data.draw.vertex_offset = offset;
command.data.draw.vertex_count = count;
command.data.draw.base_instance = instance_base;
command.data.draw.instance_count = instance_count;
commands.push_back(command);
}
void draw_indexed(int indexCount, int firstIndex, int vertexOffset) {
GFXDrawCommand command;
command.type = GFXCommandType::DrawIndexed;
command.data.draw_indexed.vertex_offset = vertexOffset;
command.data.draw_indexed.first_index = firstIndex;
command.data.draw_indexed.index_count = indexCount;
commands.push_back(command);
}
void memory_barrier() {
GFXDrawCommand command;
command.type = GFXCommandType::MemoryBarrier;
commands.push_back(command);
}
void copy_texture(GFXTexture* src, int width, int height, GFXTexture* dst, int to_slice, int to_layer, int to_level) {
GFXDrawCommand command;
command.type = GFXCommandType::CopyTexture;
command.data.copy_texture.src = src;
command.data.copy_texture.width = width;
command.data.copy_texture.height = height;
command.data.copy_texture.dst = dst;
command.data.copy_texture.to_slice = to_slice;
command.data.copy_texture.to_layer = to_layer;
command.data.copy_texture.to_level = to_level;
commands.push_back(command);
}
void set_viewport(Viewport viewport) {
GFXDrawCommand command;
command.type = GFXCommandType::SetViewport;
command.data.set_viewport.viewport = viewport;
commands.push_back(command);
}
void set_scissor(const Rectangle rect) {
GFXDrawCommand command;
command.type = GFXCommandType::SetScissor;
command.data.set_scissor.rect = rect;
commands.push_back(command);
}
void generate_mipmaps(GFXTexture* texture, int mip_count) {
GFXDrawCommand command;
command.type = GFXCommandType::GenerateMipmaps;
command.data.generate_mipmaps.texture = texture;
command.data.generate_mipmaps.mip_count = mip_count;
commands.push_back(command);
}
void set_depth_bias(const float constant, const float clamp, const float slope) {
GFXDrawCommand command;
command.type = GFXCommandType::SetDepthBias;
command.data.set_depth_bias.constant = constant;
command.data.set_depth_bias.clamp = clamp;
command.data.set_depth_bias.slope_factor = slope;
commands.push_back(command);
}
void push_group(const std::string_view name) {
GFXDrawCommand command;
command.type = GFXCommandType::PushGroup;
command.data.push_group.name = name;
commands.push_back(command);
}
void pop_group() {
GFXDrawCommand command;
command.type = GFXCommandType::PopGroup;
commands.push_back(command);
}
void insert_label(const std::string_view name) {
GFXDrawCommand command;
command.type = GFXCommandType::InsertLabel;
command.data.insert_label.name = name;
commands.push_back(command);
}
std::vector<GFXDrawCommand> commands;
};

View file

@ -0,0 +1,8 @@
#pragma once
#include "gfx_object.hpp"
class GFXFramebuffer : public GFXObject {
public:
};

View file

@ -0,0 +1,6 @@
#pragma once
class GFXObject {
public:
virtual ~GFXObject() {}
};

View file

@ -0,0 +1,7 @@
#pragma once
#include "gfx_object.hpp"
class GFXPipeline : public GFXObject {
public:
};

View file

@ -0,0 +1,8 @@
#pragma once
#include "gfx_object.hpp"
class GFXRenderPass : public GFXObject {
public:
};

View file

@ -0,0 +1,8 @@
#pragma once
#include "gfx_object.hpp"
class GFXSampler : public GFXObject {
public:
};

View file

@ -0,0 +1,8 @@
#pragma once
#include "gfx_object.hpp"
class GFXTexture : public GFXObject {
public:
int width, height;
};

View file

@ -0,0 +1,25 @@
set(HEADERS
include/gfx_vulkan.hpp
src/gfx_vulkan_buffer.hpp
src/gfx_vulkan_pipeline.hpp
src/gfx_vulkan_texture.hpp
src/gfx_vulkan_framebuffer.hpp
src/gfx_vulkan_renderpass.hpp)
find_package(Vulkan REQUIRED)
add_library(GFXVulkan STATIC
src/gfx_vulkan.cpp
${HEADERS})
target_link_libraries(GFXVulkan PUBLIC
GFX
Vulkan::Vulkan
Core
Log)
target_include_directories(GFXVulkan PUBLIC
include
PRIVATE
src)
set_target_properties(GFXVulkan PROPERTIES
CXX_STANDARD 17)

View file

@ -0,0 +1,114 @@
#pragma once
#ifdef PLATFORM_WINDOWS
#define NOMINMAX // donut define max in windows.h
#define VK_USE_PLATFORM_WIN32_KHR
#else
#define VK_USE_PLATFORM_XCB_KHR
#endif
#include <vulkan/vulkan.h>
#include <map>
#include <array>
#include "gfx.hpp"
#include "gfx_vulkan_constants.hpp"
class GFXVulkanPipeline;
class GFXVulkan : public GFX {
public:
bool is_supported() { return true; }
GFXContext required_context() { return GFXContext::Vulkan; }const char* get_name() override;
bool initialize(const GFXCreateInfo& info) override;
void initialize_view(void* native_handle, const int identifier, const uint32_t width, const uint32_t height) override;
void recreate_view(const int identifier, const uint32_t width, const uint32_t height) override;
// buffer operations
GFXBuffer* create_buffer(void* data, const GFXSize size, const bool dynamic_data, const GFXBufferUsage usage) override;
void copy_buffer(GFXBuffer* buffer, void* data, const GFXSize offset, const GFXSize size) override;
void* get_buffer_contents(GFXBuffer* buffer) override;
void release_buffer_contents(GFXBuffer* buffer, void* handle) override;
// texture operations
GFXTexture* create_texture(const GFXTextureCreateInfo& info) override;
void copy_texture(GFXTexture* texture, void* data, const GFXSize size) override;
void copy_texture(GFXTexture* from, GFXTexture* to) override;
void copy_texture(GFXTexture* from, GFXBuffer* to) override;
// framebuffer operations
GFXFramebuffer* create_framebuffer(const GFXFramebufferCreateInfo& info) override;
// render pass operations
GFXRenderPass* create_render_pass(const GFXRenderPassCreateInfo& info) override;
// pipeline operations
GFXPipeline* create_graphics_pipeline(const GFXGraphicsPipelineCreateInfo& info) override;
// misc operations
GFXSize get_alignment(const GFXSize size) override;
void render(GFXCommandBuffer* command_buffer, const int identifier) override;
private:
void createInstance(std::vector<const char*> layers, std::vector<const char*> extensions);
void createLogicalDevice(std::vector<const char*> extensions);
void createSwapchain(VkSwapchainKHR oldSwapchain = VK_NULL_HANDLE);
void createDescriptorPool();
void createSyncPrimitives();
// dynamic descriptor sets
void resetDescriptorState();
void cacheDescriptorState(GFXVulkanPipeline* pipeline, VkDescriptorSetLayout layout);
uint64_t getDescriptorHash(GFXVulkanPipeline* pipeline);
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
void transitionImageLayout(VkImage image, VkFormat format, VkImageAspectFlags aspect, VkImageLayout oldLayout, VkImageLayout newLayout);
VkShaderModule createShaderModule(const unsigned char* code, const int length);
VkCommandBuffer beginSingleTimeCommands();
void endSingleTimeCommands(VkCommandBuffer commandBuffer);
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
VkDevice device = VK_NULL_HANDLE;
VkQueue graphicsQueue = VK_NULL_HANDLE;
VkQueue presentQueue = VK_NULL_HANDLE;
VkCommandPool commandPool = VK_NULL_HANDLE;
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
uint32_t surfaceWidth = -1, surfaceHeight = -1;
std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;
size_t currentFrame = 0;
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkExtent2D swapchainExtent = {};
std::vector<VkImage> swapchainImages;
std::vector<VkImageView> swapchainImageViews;
VkRenderPass swapchainRenderPass = VK_NULL_HANDLE;
std::vector<VkFramebuffer> swapchainFramebuffers;
std::vector<VkCommandBuffer> commandBuffers;
struct BoundShaderBuffer {
GFXBuffer* buffer = nullptr;
VkDeviceSize size = 0, offset = 0;
};
std::array<BoundShaderBuffer, 25> boundShaderBuffers;
std::array<GFXTexture*, 25> boundTextures;
};

View file

@ -0,0 +1,3 @@
#pragma once
const int MAX_FRAMES_IN_FLIGHT = 3;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
#pragma once
#include <vulkan/vulkan.h>
#include "gfx_buffer.hpp"
#include "gfx_vulkan_constants.hpp"
class GFXVulkanBuffer : public GFXBuffer {
public:
bool is_dynamic_data = false;
struct Data {
VkBuffer handle;
VkDeviceMemory memory;
} data[MAX_FRAMES_IN_FLIGHT];
Data get(int frameIndex) {
if(is_dynamic_data) {
return data[frameIndex];
} else {
return data[0];
}
}
VkDeviceSize size;
};

View file

@ -0,0 +1,12 @@
#pragma once
#include <vulkan/vulkan.h>
#include "gfx_framebuffer.hpp"
class GFXVulkanFramebuffer : public GFXFramebuffer {
public:
VkFramebuffer handle;
int width = 0, height = 0;
};

View file

@ -0,0 +1,16 @@
#pragma once
#include <vulkan/vulkan.h>
#include "gfx_pipeline.hpp"
class GFXVulkanPipeline : public GFXPipeline {
public:
VkPipeline handle;
VkPipelineLayout layout;
VkDescriptorSetLayout descriptorLayout;
// dynamic descriptor sets
std::map<uint64_t, VkDescriptorSet> cachedDescriptorSets;
};

View file

@ -0,0 +1,15 @@
#pragma once
#include <vulkan/vulkan.h>
#include "gfx_renderpass.hpp"
class GFXVulkanRenderPass : public GFXRenderPass {
public:
VkRenderPass handle = VK_NULL_HANDLE;
unsigned int numAttachments = 0;
int depth_attachment = -1;
bool hasDepthAttachment = false;
};

View file

@ -0,0 +1,18 @@
#pragma once
#include <vulkan/vulkan.h>
#include "gfx_texture.hpp"
class GFXVulkanTexture : public GFXTexture {
public:
VkImage handle;
VkDeviceMemory memory;
VkImageView view;
VkSampler sampler;
int width, height;
VkFormat format;
VkImageLayout layout;
};

9
engine/log/CMakeLists.txt Executable file
View file

@ -0,0 +1,9 @@
set(SRC
include/log.hpp
src/log.cpp)
add_library(Log STATIC ${SRC})
target_include_directories(Log PUBLIC include)
target_compile_features(Log PUBLIC cxx_std_17)
set_target_properties(Log PROPERTIES CXX_EXTENSIONS OFF)
target_link_libraries(Log PRIVATE Utility)

62
engine/log/include/log.hpp Executable file
View file

@ -0,0 +1,62 @@
#pragma once
#include <string_view>
#include <string>
#include <vector>
enum class System {
None,
Core,
Renderer,
Game,
File,
GFX
};
enum class Level {
Info,
Warning,
Error,
Debug
};
namespace console {
template<typename Arg>
void internal_format(std::string& msg, const Arg& arg) {
auto pos = msg.find_first_of("{}");
msg.replace(pos, 2, arg);
}
void process_message(const Level level, const System system, const std::string_view message);
template<typename... Args>
void internal_print(const Level level, const System system, const std::string_view format, Args&&... args) {
auto msg = std::string(format);
((internal_format<Args>(msg, args)), ...);
process_message(level, system, msg);
}
template<typename... Args>
void info(const System system, const std::string_view format, Args&&... args) {
internal_print(Level::Info, system, format, args...);
}
template<typename... Args>
void warning(const System system, const std::string_view format, Args&&... args) {
internal_print(Level::Warning, system, format, args...);
}
template<typename... Args>
void error(const System system, const std::string_view format, Args&&... args) {
internal_print(Level::Error, system, format, args...);
}
template<typename... Args>
void debug(const System system, const std::string_view format, Args&&... args) {
internal_print(Level::Debug, system, format, args...);
}
inline std::vector<std::string> stored_output;
}

23
engine/log/src/log.cpp Executable file
View file

@ -0,0 +1,23 @@
#include "log.hpp"
#include <iostream>
#include <chrono>
#include <iomanip>
#include "string_utils.hpp"
#include "utility.hpp"
void console::process_message(const Level level, const System system, const std::string_view message) {
auto now = std::chrono::system_clock::now();
std::time_t t_c = std::chrono::system_clock::to_time_t(now);
std::string date;
date.resize(30);
std::strftime(&date[0], date.size(), "%Y-%m-%d %H:%M:%S", std::localtime(&t_c));
std::string s = utility::format("{} {} {}: {}", date, utility::enum_to_string(system), utility::enum_to_string(level), message);
std::cout << s << '\n';
stored_output.push_back(s);
}

15
engine/math/CMakeLists.txt Executable file
View file

@ -0,0 +1,15 @@
set(SRC
include/math.hpp
include/matrix.hpp
include/transform.hpp
include/vector.hpp
include/quaternion.hpp
include/plane.hpp
src/transform.cpp
src/math.cpp)
add_library(Math STATIC ${SRC})
target_include_directories(Math PUBLIC include)
target_compile_features(Math PUBLIC cxx_std_17)
set_target_properties(Math PROPERTIES CXX_EXTENSIONS OFF)

30
engine/math/include/math.hpp Executable file
View file

@ -0,0 +1,30 @@
#pragma once
#include "matrix.hpp"
#include "transform.hpp"
#include "vector.hpp"
constexpr double PI = 3.141592653589793;
template<typename T>
constexpr inline T radians(const T degrees) {
return degrees / static_cast<T>(180) * static_cast<T>(PI);
}
template<typename T>
constexpr inline T degrees(const T radians) {
return radians / static_cast<T>(PI) * static_cast<T>(180);
}
template<typename T>
constexpr inline bool nearly_equal(const T a, const T b) {
return std::nextafter(a, std::numeric_limits<T>::lowest()) <= b
&& std::nextafter(a, std::numeric_limits<T>::max()) >= b;
}
Matrix4x4 matrix_from_quat(const Quaternion quat);
Quaternion quat_from_matrix(const Matrix3x3 matrix);
Quaternion euler_to_quat(const Vector3 angle);
Vector3 quat_to_euler(const Quaternion quat);
Matrix4x4 inverse(const Matrix4x4 m);
Quaternion angle_axis(const float angle, const Vector3 axis);

87
engine/math/include/matrix.hpp Executable file
View file

@ -0,0 +1,87 @@
#pragma once
#include "vector.hpp"
// m = rows
// n = columns
// row major storage mode?
template<class T, std::size_t M, std::size_t N>
class Matrix {
public:
constexpr Matrix(const T diagonal = T(1)) {
for(std::size_t i = 0; i < (M * N); i++)
unordered_data[i] = ((i / M) == (i % N) ? diagonal : T(0));
}
constexpr Matrix(const Vector4 m1_, const Vector4 m2_, const Vector4 m3_, const Vector4 m4_) {
columns[0] = m1_;
columns[1] = m2_;
columns[2] = m3_;
columns[3] = m4_;
}
constexpr inline void operator*=(const Matrix rhs);
using VectorType = Vector<N, float>;
constexpr VectorType& operator[](const size_t index) { return columns[index]; }
constexpr VectorType operator[](const size_t index) const { return columns[index]; }
union {
VectorType columns[M];
T data[M][N];
T unordered_data[M * N] = {};
};
};
using Matrix4x4 = Matrix<float, 4, 4>;
using Matrix3x3 = Matrix<float, 3, 3>;
constexpr inline Matrix4x4 operator*(const Matrix4x4 lhs, const Matrix4x4 rhs) {
Matrix4x4 tmp(0.0f);
for(int j = 0; j < 4; j++) {
for(int i = 0; i < 4; i++) {
for(int k = 0; k < 4; k++)
tmp.data[j][i] += lhs.data[k][i] * rhs.data[j][k];
}
}
return tmp;
}
constexpr inline Vector4 operator*(const Matrix4x4 lhs, const Vector4 rhs) {
Vector4 tmp;
for(int r = 0; r < 4; r++) {
for(int c = 0; c < 4; c++)
tmp[r] += lhs.data[c][r] * rhs.data[c];
}
return tmp;
}
constexpr inline Matrix4x4 operator/(const Matrix4x4 lhs, const float rhs) {
Matrix4x4 tmp(0.0f);
for(int j = 0; j < 4; j++) {
for(int i = 0; i < 4; i++) {
for(int k = 0; k < 4; k++)
tmp.data[k][i] = lhs.data[k][i] / rhs;
}
}
return tmp;
}
constexpr inline Matrix4x4 operator*(const Matrix4x4 lhs, const float rhs) {
Matrix4x4 tmp(0.0f);
for (int j = 0; j < 4; j++) {
for (int i = 0; i < 4; i++) {
for (int k = 0; k < 4; k++)
tmp.data[k][i] = lhs.data[k][i] * rhs;
}
}
return tmp;
}
template<typename T, std::size_t M, std::size_t N>
constexpr void Matrix<T, M, N>::operator*=(const Matrix<T, M, N> rhs) { *this = *this * rhs; }

26
engine/math/include/plane.hpp Executable file
View file

@ -0,0 +1,26 @@
#pragma once
struct Plane {
float a = 0.0, b = 0.0, c = 0.0, d = 0.0;
};
inline Plane normalize(const Plane& plane) {
const float magnitude = std::sqrt(plane.a * plane.a +
plane.b * plane.b +
plane.c * plane.c);
Plane normalized_plane = plane;
normalized_plane.a = plane.a / magnitude;
normalized_plane.b = plane.b / magnitude;
normalized_plane.c = plane.c / magnitude;
normalized_plane.d = plane.d / magnitude;
return normalized_plane;
}
inline float distance_to_point(const Plane& plane, const Vector3& point) {
return plane.a * point.x +
plane.b * point.y +
plane.c * point.z +
plane.d;
}

View file

@ -0,0 +1,87 @@
#pragma once
#include "vector.hpp"
class Quaternion {
public:
constexpr Quaternion(const float v = 0.0f) : w(1.0f), x(v), y(v), z(v) {}
constexpr Quaternion(const float x, const float y, const float z, const float w) : w(w), x(x), y(y), z(z) {}
float w, x, y, z;
};
constexpr inline Quaternion operator*(const Quaternion lhs, const float rhs) {
Quaternion tmp(lhs);
tmp.x *= rhs;
tmp.y *= rhs;
tmp.z *= rhs;
tmp.w *= rhs;
return tmp;
}
constexpr inline Vector3 operator*(const Quaternion& lhs, const Vector3& rhs) {
const Vector3 quatVector = Vector3(lhs.x, lhs.y, lhs.z);
const Vector3 uv = cross(quatVector, rhs);
const Vector3 uuv = cross(quatVector, uv);
return rhs + ((uv * lhs.w) + uuv) * 2.0f;
}
constexpr inline Vector3 operator*(const Vector3& rhs, const Quaternion& lhs) {
return lhs * rhs;
}
constexpr inline float dot(const Quaternion& a, const Quaternion& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
inline float length(const Quaternion& quat) {
return sqrtf(dot(quat, quat));
}
inline Quaternion normalize(const Quaternion& quat) {
const float l = length(quat);
return quat * (1.0f / l);
}
constexpr inline Quaternion lerp(const Quaternion& a, const Quaternion& b, const float time) {
float cosom = dot(a, b);
Quaternion end = b;
if(cosom < 0.0f) {
cosom = -cosom;
end.x = -end.x;
end.y = -end.y;
end.z = -end.z;
end.w = -end.w;
}
float sclp = 0.0f, sclq = 0.0f;
if((1.0f - cosom) > 0.0001f) {
const float omega = std::acos(cosom);
const float sinom = std::sin(omega);
sclp = std::sin((1.0f - time) * omega) / sinom;
sclq = std::sin(time* omega) / sinom;
} else {
sclp = 1.0f - time;
sclq = time;
}
return {sclp * a.x + sclq * end.x,
sclp * a.y + sclq * end.y,
sclp * a.z + sclq * end.z,
sclp * a.w + sclq * end.w};
}
constexpr inline Quaternion operator*(const Quaternion lhs, const Quaternion rhs) {
Quaternion tmp;
tmp.w = lhs.w * rhs.w - lhs.x * rhs.x - lhs.y * rhs.y - lhs.z * rhs.z;
tmp.x = lhs.w * rhs.x + lhs.x * rhs.w + lhs.y * rhs.z - lhs.z * rhs.y;
tmp.y = lhs.w * rhs.y + lhs.y * rhs.w + lhs.z * rhs.x - lhs.x * rhs.z;
tmp.z = lhs.w * rhs.z + lhs.z * rhs.w + lhs.x * rhs.y - lhs.y * rhs.x;
return tmp;
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "matrix.hpp"
#include "vector.hpp"
#include "quaternion.hpp"
namespace transform {
/*
Produces a right-handed perspective matrix.
*/
Matrix4x4 perspective(const float fov, const float aspectRatio, const float zNear);
Matrix4x4 orthographic(float left, float right, float bottom, float top, float zNear, float zFar);
/*
Produces right-handed outputs.
*/
Matrix4x4 look_at(const Vector3 eye, const Vector3 center, const Vector3 up);
Quaternion quat_look_at(const Vector3 eye, const Vector3 center, const Vector3 up);
Matrix4x4 translate(const Matrix4x4 matrix, const Vector3 translation);
Matrix4x4 rotate(const Matrix4x4 matrix, const float angle, const Vector3 axis);
Matrix4x4 scale(const Matrix4x4 matrix, const Vector3 scale);
}

251
engine/math/include/vector.hpp Executable file
View file

@ -0,0 +1,251 @@
#pragma once
#include <cmath>
#include <type_traits>
#include <typeinfo>
#include <array>
#define DEFINE_OPERATORS(Size) \
constexpr Vector(const T scalar = T(0)) { \
for(std::size_t i = 0; i < Size; i++) \
data[i] = scalar; \
} \
constexpr T& operator[](const size_t index) { \
return data[index]; \
} \
constexpr T operator[](const size_t index) const { \
return data[index]; \
} \
constexpr Vector& operator+=(const Vector rhs) { \
for(std::size_t i = 0; i < Size; i++)\
data[i] += rhs[i]; \
return *this; \
} \
constexpr Vector& operator-=(const Vector rhs) { \
for(std::size_t i = 0; i < Size; i++)\
data[i] -= rhs[i]; \
return *this; \
} \
constexpr Vector& operator*=(const Vector rhs) { \
for(std::size_t i = 0; i < Size; i++) \
data[i] *= rhs[i]; \
return *this; \
} \
constexpr Vector& operator/=(const T scalar) { \
for(std::size_t i = 0; i < Size; i++) \
data[i] /= scalar; \
return *this; \
} \
constexpr T* ptr() const { \
return data.data(); \
} \
constexpr T* ptr() { \
return data.data(); \
} \
constexpr Vector operator- () const { \
Vector vec; \
for(std::size_t i = 0; i < Size; i++) \
vec[i] = -data[i]; \
return vec; \
}
template<std::size_t N, class T>
struct Vector {
std::array<T, N> data;
};
template<class T>
struct Vector<2, T> {
constexpr Vector(const T x_, const T y_) {
data[0] = x_;
data[1] = y_;
}
DEFINE_OPERATORS(2)
union {
std::array<T, 2> data;
struct {
T x, y;
};
};
};
using Vector2 = Vector<2, float>;
template<class T>
struct Vector<3, T> {
constexpr Vector(const T x_, const T y_, const T z_) {
data[0] = x_;
data[1] = y_;
data[2] = z_;
}
DEFINE_OPERATORS(3)
union {
std::array<T, 3> data;
struct {
T x, y, z;
};
};
};
using Vector3 = Vector<3, float>;
template<class T>
struct alignas(16) Vector<4, T> {
constexpr Vector(const T x_, const T y_, const T z_, const T w_) {
data[0] = x_;
data[1] = y_;
data[2] = z_;
data[3] = w_;
}
constexpr Vector(const Vector3& v, const float w = 0.0f) : x(v.x), y(v.y), z(v.z), w(w) {}
DEFINE_OPERATORS(4)
union {
std::array<T, 4> data;
struct {
T x, y, z, w;
};
struct {
Vector<3, T> xyz;
T padding_xyz;
};
};
};
using Vector4 = Vector<4, float>;
template<std::size_t N, class T>
constexpr inline bool operator==(const Vector<N, T> lhs, const Vector<N, T> rhs) {
bool is_equal = true;
for(std::size_t i = 0; i < N; i++) {
if(lhs[i] != rhs[i])
is_equal = false;
}
return is_equal;
}
template<std::size_t N, class T>
constexpr inline bool operator!=(const Vector<N, T> lhs, const Vector<N, T> rhs) {
return !(lhs == rhs);
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> operator-(const Vector<N, T> lhs, const Vector<N, T> rhs) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = lhs[i] - rhs[i];
return vec;
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> operator+(const Vector<N, T> lhs, const Vector<N, T> rhs) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = lhs[i] + rhs[i];
return vec;
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> operator*(const Vector<N, T> lhs, const Vector<N, T> rhs) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = lhs[i] * rhs[i];
return vec;
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> operator*(const Vector<N, T> lhs, const T scalar) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = lhs[i] * scalar;
return vec;
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> operator/(const Vector<N, T> lhs, const Vector<N, T> rhs) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = lhs[i] / rhs[i];
return vec;
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> operator/(const Vector<N, T> lhs, const T scalar) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = lhs[i] / scalar;
return vec;
}
template<std::size_t N, class T>
constexpr inline T dot(const Vector<N, T> lhs, const Vector<N, T> rhs) {
T product = T(0.0);
for(std::size_t i = 0; i < N; i++)
product += lhs[i] * rhs[i];
return product;
}
template<std::size_t N, class T>
constexpr inline T length(const Vector<N, T> vec) {
return sqrtf(dot(vec, vec));
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> lerp(const Vector<N, T> a, const Vector<N, T> b, const T t) {
Vector<N, T> vec;
for(std::size_t i = 0; i < N; i++)
vec[i] = (T(1) - t) * a[i] + t * b[i];
return vec;
}
template<std::size_t N, class T>
constexpr inline Vector<N, T> normalize(const Vector<N, T> vec) {
Vector<N, T> result;
const float len = length(vec);
for(std::size_t i = 0; i < N; i++)
result[i] = vec[i] / len;
return result;
}
constexpr inline Vector3 cross(const Vector3 u, const Vector3 v) {
return Vector3(u.y * v.z - u.z * v.y, u.z * v.x - u.x * v.z, u.x * v.y - u.y * v.x);
}
inline float distance(const Vector3 lhs, const Vector3 rhs) {
const float diffX = lhs.x - rhs.x;
const float diffY = lhs.y - rhs.y;
const float diffZ = lhs.z - rhs.z;
return std::sqrt((diffX * diffX) + (diffY * diffY) + (diffZ * diffZ));
}
template<std::size_t N, class T>
constexpr inline T norm(const Vector<N, T> vec) {
T val = T(0);
for(std::size_t i = 0; i < N; i++)
val += abs(vec[i]);
return sqrtf(dot(vec, vec));
}

169
engine/math/src/math.cpp Executable file
View file

@ -0,0 +1,169 @@
#include "math.hpp"
Matrix4x4 matrix_from_quat(const Quaternion quat) {
const float qxx = quat.x * quat.x;
const float qyy = quat.y * quat.y;
const float qzz = quat.z * quat.z;
const float qxz = quat.x * quat.z;
const float qxy = quat.x * quat.y;
const float qyz = quat.y * quat.z;
const float qwx = quat.w * quat.x;
const float qwy = quat.w * quat.y;
const float qwz = quat.w * quat.z;
Matrix4x4 result(1.0f);
result[0][0] = 1.0f - 2.0f * (qyy + qzz);
result[0][1] = 2.0f * (qxy + qwz);
result[0][2] = 2.0f * (qxz - qwy);
result[1][0] = 2.0f * (qxy - qwz);
result[1][1] = 1.0f - 2.0f * (qxx + qzz);
result[1][2] = 2.0f * (qyz + qwx);
result[2][0] = 2.0f * (qxz + qwy);
result[2][1] = 2.0f * (qyz - qwx);
result[2][2] = 1.0f - 2.0f * (qxx + qyy);
return result;
}
Quaternion quat_from_matrix(const Matrix3x3 m) {
const float fourXSquaredMinus1 = m[0][0] - m[1][1] - m[2][2];
const float fourYSquaredMinus1 = m[1][1] - m[0][0] - m[2][2];
const float fourZSquaredMinus1 = m[2][2] - m[0][0] - m[1][1];
const float fourWSquaredMinus1 = m[0][0] + m[1][1] + m[2][2];
int biggestIndex = 0;
float fourBiggestSquaredMinus1 = fourWSquaredMinus1;
if(fourXSquaredMinus1 > fourBiggestSquaredMinus1) {
fourBiggestSquaredMinus1 = fourXSquaredMinus1;
biggestIndex = 1;
}
if(fourYSquaredMinus1 > fourBiggestSquaredMinus1) {
fourBiggestSquaredMinus1 = fourYSquaredMinus1;
biggestIndex = 2;
}
if(fourZSquaredMinus1 > fourBiggestSquaredMinus1) {
fourBiggestSquaredMinus1 = fourZSquaredMinus1;
biggestIndex = 3;
}
const float biggestVal = sqrt(fourBiggestSquaredMinus1 + 1.0f) * 0.5f;
const float mult = 0.25f / biggestVal;
switch(biggestIndex) {
case 0:
return Quaternion((m[1][2] - m[2][1]) * mult, (m[2][0] - m[0][2]) * mult, (m[0][1] - m[1][0]) * mult, biggestVal);
case 1:
return Quaternion(biggestVal, (m[0][1] + m[1][0]) * mult, (m[2][0] + m[0][2]) * mult, (m[1][2] - m[2][1]) * mult);
case 2:
return Quaternion((m[0][1] + m[1][0]) * mult, biggestVal, (m[1][2] + m[2][1]) * mult, (m[2][0] - m[0][2]) * mult);
case 3:
return Quaternion((m[2][0] + m[0][2]) * mult, (m[1][2] + m[2][1]) * mult, biggestVal, (m[0][1] - m[1][0]) * mult);
}
}
Quaternion euler_to_quat(const Vector3 angle) {
const float cy = cosf(angle.z * 0.5f);
const float sy = sinf(angle.z * 0.5f);
const float cr = cosf(angle.x * 0.5f);
const float sr = sinf(angle.x * 0.5f);
const float cp = cosf(angle.y * 0.5f);
const float sp = sinf(angle.y * 0.5f);
const float cpcy = cp * cy;
const float spcy = sp * cy;
const float cpsy = cp * sy;
const float spsy = sp * sy;
Quaternion result;
result.w = cr * cpcy + sr * spsy;
result.x = sr * cpcy - cr * spsy;
result.y = cr * spcy + sr * cpsy;
result.z = cr * cpsy - sr * spcy;
return normalize(result);
}
Vector3 quat_to_euler(const Quaternion q) {
const float roll = atan2(2.0f * (q.x * q.y + q.w * q.z), q.w * q.w + q.x * q.x - q.y * q.y - q.z * q.z);
const float y = 2.0f * (q.y * q.z + q.w * q.x);
const float x = q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z;
const float pitch = atan2(y, x);
const float yaw = asinf(-2.0f * (q.x * q.z - q.w * q.y));
return Vector3(pitch, yaw, roll);
}
Matrix4x4 inverse(const Matrix4x4 m) {
const float Coef00 = m[2][2] * m[3][3] - m[3][2] * m[2][3];
const float Coef02 = m[1][2] * m[3][3] - m[3][2] * m[1][3];
const float Coef03 = m[1][2] * m[2][3] - m[2][2] * m[1][3];
const float Coef04 = m[2][1] * m[3][3] - m[3][1] * m[2][3];
const float Coef06 = m[1][1] * m[3][3] - m[3][1] * m[1][3];
const float Coef07 = m[1][1] * m[2][3] - m[2][1] * m[1][3];
const float Coef08 = m[2][1] * m[3][2] - m[3][1] * m[2][2];
const float Coef10 = m[1][1] * m[3][2] - m[3][1] * m[1][2];
const float Coef11 = m[1][1] * m[2][2] - m[2][1] * m[1][2];
const float Coef12 = m[2][0] * m[3][3] - m[3][0] * m[2][3];
const float Coef14 = m[1][0] * m[3][3] - m[3][0] * m[1][3];
const float Coef15 = m[1][0] * m[2][3] - m[2][0] * m[1][3];
const float Coef16 = m[2][0] * m[3][2] - m[3][0] * m[2][2];
const float Coef18 = m[1][0] * m[3][2] - m[3][0] * m[1][2];
const float Coef19 = m[1][0] * m[2][2] - m[2][0] * m[1][2];
const float Coef20 = m[2][0] * m[3][1] - m[3][0] * m[2][1];
const float Coef22 = m[1][0] * m[3][1] - m[3][0] * m[1][1];
const float Coef23 = m[1][0] * m[2][1] - m[2][0] * m[1][1];
const Vector4 Fac0(Coef00, Coef00, Coef02, Coef03);
const Vector4 Fac1(Coef04, Coef04, Coef06, Coef07);
const Vector4 Fac2(Coef08, Coef08, Coef10, Coef11);
const Vector4 Fac3(Coef12, Coef12, Coef14, Coef15);
const Vector4 Fac4(Coef16, Coef16, Coef18, Coef19);
const Vector4 Fac5(Coef20, Coef20, Coef22, Coef23);
const Vector4 Vec0(m[1][0], m[0][0], m[0][0], m[0][0]);
const Vector4 Vec1(m[1][1], m[0][1], m[0][1], m[0][1]);
const Vector4 Vec2(m[1][2], m[0][2], m[0][2], m[0][2]);
const Vector4 Vec3(m[1][3], m[0][3], m[0][3], m[0][3]);
const Vector4 Inv0(Vec1 * Fac0 - Vec2 * Fac1 + Vec3 * Fac2);
const Vector4 Inv1(Vec0 * Fac0 - Vec2 * Fac3 + Vec3 * Fac4);
const Vector4 Inv2(Vec0 * Fac1 - Vec1 * Fac3 + Vec3 * Fac5);
const Vector4 Inv3(Vec0 * Fac2 - Vec1 * Fac4 + Vec2 * Fac5);
const Vector4 SignA(+1, -1, +1, -1);
const Vector4 SignB(-1, +1, -1, +1);
const Matrix4x4 Inverse(Inv0 * SignA, Inv1 * SignB, Inv2 * SignA, Inv3 * SignB);
const Vector4 Row0(Inverse[0][0], Inverse[1][0], Inverse[2][0], Inverse[3][0]);
const Vector4 Dot0(m[0] * Row0);
const float Dot1 = (Dot0.x + Dot0.y) + (Dot0.z + Dot0.w);
const float OneOverDeterminant = 1.0f / Dot1;
return Inverse * OneOverDeterminant;
}
Quaternion angle_axis(const float angle, const Vector3 axis) {
const float s = sinf(angle * 0.5f);
Quaternion result;
result.w = cosf(angle * 0.5f);
result.x = axis.x * s;
result.y = axis.y * s;
result.z = axis.z * s;
return result;
}

112
engine/math/src/transform.cpp Executable file
View file

@ -0,0 +1,112 @@
#include "transform.hpp"
#include <cmath>
#include "math.hpp"
Matrix4x4 transform::perspective(const float fov, const float aspect, const float zNear) {
const float range = tanf(fov / 2.0f) * zNear;
const float left = -range * aspect;
const float right = range * aspect;
const float bottom = -range;
const float top = range;
Matrix4x4 result(0.0f);
result[0][0] = (2.0f * zNear) / (right - left);
result[1][1] = (2.0f * zNear) / (top - bottom);
result[2][2] = -1.0f;
result[2][3] = -1.0f;
result[3][2] = -2.0 * zNear;
return result;
}
Matrix4x4 transform::orthographic(float left, float right, float bottom, float top, float zNear, float zFar) {
Matrix4x4 result(1.0f);
result[0][0] = 2.0f / (right - left);
result[1][1] = 2.0f / (top - bottom);
result[2][2] = - 1.0f / (zFar - zNear);
result[3][0] = -(right + left) / (right - left);
result[3][1] = -(top + bottom) / (top - bottom);
result[3][2] = - zNear / (zFar - zNear);
return result;
}
Matrix4x4 transform::look_at(const Vector3 eye, const Vector3 center, const Vector3 up) {
const Vector3 f = normalize(center - eye);
const Vector3 s = normalize(cross(f, up));
const Vector3 u = cross(s, f);
Matrix4x4 result(1.0f);
result[0][0] = s.x;
result[1][0] = s.y;
result[2][0] = s.z;
result[0][1] = u.x;
result[1][1] = u.y;
result[2][1] = u.z;
result[0][2] = -f.x;
result[1][2] = -f.y;
result[2][2] = -f.z;
result[3][0] = -dot(s, eye);
result[3][1] = -dot(u, eye);
result[3][2] = dot(f, eye);
return result;
}
Matrix4x4 transform::translate(const Matrix4x4 matrix, const Vector3 translation) {
Matrix4x4 result(1.0f);
result[3] = Vector4(translation, 1.0);
return matrix * result;
}
Matrix4x4 transform::rotate(const Matrix4x4 matrix, const float angle, const Vector3 v) {
const float a = angle;
const float c = cosf(a);
const float s = sinf(a);
const Vector3 axis = normalize(v);
const Vector3 temp = Vector3((1.0f - c) * axis);
Matrix4x4 Rotate(1.0f);
Rotate[0][0] = c + temp[0] * axis[0];
Rotate[0][1] = temp[0] * axis[1] + s * axis[2];
Rotate[0][2] = temp[0] * axis[2] - s * axis[1];
Rotate[1][0] = temp[1] * axis[0] - s * axis[2];
Rotate[1][1] = c + temp[1] * axis[1];
Rotate[1][2] = temp[1] * axis[2] + s * axis[0];
Rotate[2][0] = temp[2] * axis[0] + s * axis[1];
Rotate[2][1] = temp[2] * axis[1] - s * axis[0];
Rotate[2][2] = c + temp[2] * axis[2];
Matrix4x4 result(1.0f);
result[0] = Rotate[0][0] + Rotate[0][1] + Rotate[0][2];
result[1] = Rotate[1][0] + Rotate[1][1] + Rotate[1][2];
result[2] = Rotate[2][0] + Rotate[2][1] + Rotate[2][2];
return matrix * result;
}
Matrix4x4 transform::scale(const Matrix4x4 matrix, const Vector3 scale) {
Matrix4x4 result(1.0f);
result[0][0] = scale.x;
result[1][1] = scale.y;
result[2][2] = scale.z;
return matrix * result;
}
Quaternion transform::quat_look_at(const Vector3 eye, const Vector3 center, const Vector3 up) {
const Vector3 direction = normalize(center - eye);
Matrix3x3 result(1.0f);
result[2] = -direction;
result[0] = cross(up, result[2]);
result[1] = cross(result[2], result[0]);
return quat_from_matrix(result);
}

110
engine/renderer/CMakeLists.txt Executable file
View file

@ -0,0 +1,110 @@
set(SRC
include/renderer.hpp
include/font.hpp
include/pass.hpp
include/gaussianhelper.hpp
include/shadowpass.hpp
include/imguipass.hpp
include/smaapass.hpp
include/scenecapture.hpp
include/shadercompiler.hpp
include/DirStackIncluder.h
include/dofpass.hpp
src/renderer.cpp
src/gaussianhelper.cpp
src/shadowpass.cpp
src/imguipass.cpp
src/smaapass.cpp
src/scenecapture.cpp
src/shadercompiler.cpp
src/dofpass.cpp)
if(NOT ENABLE_IOS AND NOT ENABLE_TVOS)
find_package(spirv_cross_core REQUIRED)
find_package(spirv_cross_glsl REQUIRED)
find_package(spirv_cross_cpp REQUIRED)
find_package(spirv_cross_msl REQUIRED)
find_package(glslang REQUIRED)
set(CROSS_LIBS
glslang::glslang
glslang::SPIRV
glslang::OSDependent
glslang::OGLCompiler
glslang::HLSL
spirv-cross-core
spirv-cross-glsl
spirv-cross-cpp
spirv-cross-msl
)
else()
set(CROSS_LIBS
spirv-cross-core
spirv-cross-glsl
spirv-cross-cpp
spirv-cross-msl
glslang
SPIRV
)
endif()
add_library(Renderer STATIC ${SRC})
target_link_libraries(Renderer
PUBLIC
GFX
Math
PRIVATE
stb
Math
Utility
Core
imgui
SMAA::SMAA
${CROSS_LIBS})
target_include_directories(Renderer PUBLIC include)
set_target_properties(Renderer PROPERTIES CXX_STANDARD 17)
target_compile_features(Renderer PUBLIC cxx_std_17)
set_target_properties(Renderer PROPERTIES CXX_EXTENSIONS OFF)
include(../../cmake/BuildShaders.cmake)
add_shader(TARGET Renderer
SHADERS
shaders/mesh.vert.nocompile.glsl
shaders/post.vert.glsl
shaders/post.frag.glsl
shaders/text.vert.glsl
shaders/text.frag.glsl
shaders/ui.vert.glsl
shaders/ui.frag.glsl
shaders/imgui.vert.glsl
shaders/imgui.frag.glsl
shaders/debug.vert.glsl
shaders/debug.frag.glsl
shaders/gaussian.vert.glsl
shaders/gaussian.frag.glsl
shaders/shadow.vert.nocompile.glsl
shaders/shadow.frag.glsl
shaders/color.vert.glsl
shaders/color.frag.glsl
shaders/omnishadow.frag.glsl
shaders/edge.vert.glsl
shaders/edge.frag.glsl
shaders/blend.vert.glsl
shaders/blend.frag.glsl
shaders/sky.vert.glsl
shaders/sky.frag.glsl
shaders/billboard.vert.glsl
shaders/billboard.frag.glsl
shaders/scenecapture.vert.nocompile.glsl
shaders/irradiance.vert.glsl
shaders/irradiance.frag.glsl
shaders/filter.vert.glsl
shaders/filter.frag.glsl
shaders/brdf.vert.glsl
shaders/brdf.frag.glsl
shaders/rendering.nocompile.glsl
shaders/common.nocompile.glsl
shaders/dof.vert.glsl
shaders/dof.frag.glsl)

View file

@ -0,0 +1,141 @@
//
// Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
// Copyright (C) 2017 Google, Inc.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
#pragma once
#include <vector>
#include <string>
#include <fstream>
#include <algorithm>
#include <glslang/Public/ShaderLang.h>
#include "file.hpp"
// Default include class for normal include convention of search backward
// through the stack of active include paths (for nested includes).
// Can be overridden to customize.
class DirStackFileIncluder : public glslang::TShader::Includer {
public:
DirStackFileIncluder() : externalLocalDirectoryCount(0) { }
virtual IncludeResult* includeLocal(const char* headerName,
const char* includerName,
size_t inclusionDepth) override
{
return readLocalPath(headerName, includerName, (int)inclusionDepth);
}
virtual IncludeResult* includeSystem(const char* headerName,
const char* /*includerName*/,
size_t /*inclusionDepth*/) override
{
return readSystemPath(headerName);
}
// Externally set directories. E.g., from a command-line -I<dir>.
// - Most-recently pushed are checked first.
// - All these are checked after the parse-time stack of local directories
// is checked.
// - This only applies to the "local" form of #include.
// - Makes its own copy of the path.
virtual void pushExternalLocalDirectory(const std::string& dir)
{
directoryStack.push_back(dir);
externalLocalDirectoryCount = (int)directoryStack.size();
}
virtual void releaseInclude(IncludeResult* result) override
{
if (result != nullptr) {
delete [] static_cast<tUserDataElement*>(result->userData);
delete result;
}
}
virtual ~DirStackFileIncluder() override { }
protected:
typedef char tUserDataElement;
std::vector<std::string> directoryStack;
int externalLocalDirectoryCount;
// Search for a valid "local" path based on combining the stack of include
// directories and the nominal name of the header.
virtual IncludeResult* readLocalPath(const char* headerName, const char* includerName, int depth)
{
// Discard popped include directories, and
// initialize when at parse-time first level.
directoryStack.resize(depth + externalLocalDirectoryCount);
if (depth == 1)
directoryStack.back() = getDirectory(includerName);
// Find a directory that works, using a reverse search of the include stack.
for (auto it = directoryStack.rbegin(); it != directoryStack.rend(); ++it) {
std::string path = *it + '/' + headerName;
std::replace(path.begin(), path.end(), '\\', '/');
std::ifstream file(path, std::ios_base::binary | std::ios_base::ate);
if (file) {
directoryStack.push_back(getDirectory(path));
return newIncludeResult(path, file, (int)file.tellg());
}
}
return nullptr;
}
// Search for a valid <system> path.
// Not implemented yet; returning nullptr signals failure to find.
virtual IncludeResult* readSystemPath(const char* /*headerName*/) const
{
return nullptr;
}
// Do actual reading of the file, filling in a new include result.
virtual IncludeResult* newIncludeResult(const std::string& path, std::ifstream& file, int length) const
{
char* content = new tUserDataElement [length];
file.seekg(0, file.beg);
file.read(content, length);
return new IncludeResult(path, content, length, content);
}
// If no path markers, return current working directory.
// Otherwise, strip file name and return path leading up to it.
virtual std::string getDirectory(const std::string) const {
return file::get_domain_path(file::Domain::Internal);
}
};

View file

@ -0,0 +1,22 @@
#pragma once
class GFX;
class GFXCommandBuffer;
class GFXFramebuffer;
class GFXPipeline;
class GFXRenderPass;
class GFXTexture;
class Scene;
class Renderer;
class DoFPass {
public:
DoFPass(GFX* gfx, Renderer* renderer);
void render(GFXCommandBuffer* command_buffer, Scene& scene);
private:
Renderer* renderer = nullptr;
GFXPipeline* pipeline = nullptr;
};

View file

@ -0,0 +1,34 @@
#pragma once
constexpr auto numGlyphs = 95, maxInstances = 11395;
constexpr auto fontSize = 0;
struct FontChar {
unsigned short x0, y0, x1, y1;
float xoff, yoff, xadvance;
float xoff2, yoff2;
};
struct Font {
int width, height;
int ascent, descent, gap;
FontChar sizes[2][numGlyphs];
float ascentSizes[2];
};
inline Font font;
inline float get_string_width(std::string s) {
float t = 0.0f;
for(size_t i = 0; i < s.length(); i++) {
auto index = s[i] - 32;
t += font.sizes[fontSize][index].xadvance;
}
return t;
}
inline float get_font_height() {
return font.ascentSizes[fontSize];
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "common.hpp"
class GFX;
class GFXCommandBuffer;
class GFXFramebuffer;
class GFXPipeline;
class GFXRenderPass;
class GFXTexture;
class GaussianHelper {
public:
GaussianHelper(GFX* gfx, const Extent extent);
GFXTexture* render(GFXCommandBuffer* commandBuffer,GFXTexture* source);
GFXPipeline* pipeline = nullptr;
GFXRenderPass* renderPass = nullptr;
GFXTexture* texA = nullptr, *texB = nullptr;
GFXFramebuffer* fboA = nullptr, *fboB = nullptr;
private:
Extent extent;
};

View file

@ -0,0 +1,35 @@
#pragma once
#include <cstddef>
#include <string_view>
#include "pass.hpp"
class GFXBuffer;
class GFXCommandBuffer;
class GFXPipeline;
class GFXTexture;
struct ImDrawData;
class ImGuiPass : public Pass {
public:
void initialize() override;
void resize(const Extent extent) override;
void render_post(GFXCommandBuffer* command_buffer, const int index) override;
private:
void load_font(const std::string_view filename);
void create_font_texture();
void update_buffers(const ImDrawData& draw_data);
GFXPipeline* pipeline = nullptr;
GFXTexture* font_texture = nullptr;
GFXBuffer* vertex_buffer = nullptr;
int current_vertex_size = 0;
GFXBuffer* index_buffer = nullptr;
int current_index_size = 0;
};

View file

@ -0,0 +1,28 @@
#pragma once
#include "common.hpp"
class GFXCommandBuffer;
class Scene;
enum class PassTextureType {
SelectionSobel
};
class GFXTexture;
class Pass {
public:
virtual ~Pass() {}
virtual void initialize() {}
virtual void resize([[maybe_unused]] const Extent extent) {}
virtual void render_scene([[maybe_unused]] Scene& scene,
[[maybe_unused]] GFXCommandBuffer* commandBuffer) {}
virtual void render_post([[maybe_unused]] GFXCommandBuffer* commandBuffer,
[[maybe_unused]] int index) {}
virtual GFXTexture* get_requested_texture([[maybe_unused]] PassTextureType type) { return nullptr; }
};

View file

@ -0,0 +1,230 @@
#pragma once
#include <string_view>
#include <vector>
#include "file.hpp"
#include "pass.hpp"
#include "matrix.hpp"
#include "object.hpp"
#include "dofpass.hpp"
#include "common.hpp"
namespace ui {
class Screen;
}
class GFX;
class GFXBuffer;
class GFXCommandBuffer;
class GFXFramebuffer;
class GFXPipeline;
class GFXRenderPass;
class GFXTexture;
class SMAAPass;
class ShadowPass;
class SceneCapture;
class GaussianHelper;
class DoFPass;
class Scene;
struct MeshData;
struct Camera;
constexpr int max_scene_materials = 25, max_scene_lights = 25;
struct RenderScreenOptions {
bool render_world = false;
Matrix4x4 mvp;
};
constexpr int brdf_resolution = 512;
constexpr bool default_enable_aa = true;
enum class ShadowFilter {
None,
PCF,
PCSS
};
#if defined(PLATFORM_TVOS) || defined(PLATFORM_IOS)
constexpr bool default_enable_ibl = false;
constexpr bool default_enable_normal_mapping = false;
constexpr bool default_enable_point_shadows = false;
constexpr ShadowFilter default_shadow_filter = ShadowFilter::PCF;
constexpr int default_shadow_resolution = 1024;
#else
constexpr bool default_enable_ibl = true;
constexpr bool default_enable_normal_mapping = true;
constexpr bool default_enable_point_shadows = true;
constexpr ShadowFilter default_shadow_filter = ShadowFilter::PCSS;
constexpr int default_shadow_resolution = 2048;
#endif
struct RenderOptions {
bool dynamic_resolution = false;
double render_scale = 1.0f;
int shadow_resolution = default_shadow_resolution;
bool enable_aa = default_enable_aa;
bool enable_ibl = default_enable_ibl;
bool enable_normal_mapping = default_enable_normal_mapping;
bool enable_normal_shadowing = default_enable_normal_mapping;
bool enable_point_shadows = default_enable_point_shadows;
ShadowFilter shadow_filter = default_shadow_filter;
bool enable_extra_passes = true;
bool enable_frustum_culling = true;
};
inline RenderOptions render_options;
class Material;
class Renderer {
public:
Renderer(GFX* gfx, const bool enable_imgui = true);
void resize(const Extent extent);
void resize_viewport(const Extent extent);
void set_screen(ui::Screen* screen);
void init_screen(ui::Screen* screen);
void update_screen();
void startCrossfade();
void startSceneBlur();
void stopSceneBlur();
float fade = 0.0f;
bool fading = false;
bool blurring = false;
bool hasToStore = true;
int blurFrame = 0;
GFXTexture* blurStore = nullptr;
struct ControllerContinuity {
int elementOffset = 0;
};
void render(Scene* scene, int index);
void render_screen(GFXCommandBuffer* commandBuffer, ui::Screen* screen, ControllerContinuity& continuity, RenderScreenOptions options = RenderScreenOptions());
void render_camera(GFXCommandBuffer* command_buffer, Scene& scene, Object camera_object, Camera& camera, ControllerContinuity& continuity);
void create_mesh_pipeline(Material& material);
// passes
template<class T, typename... Args>
T* addPass(Args&&... args) {
auto t = std::make_unique<T>(args...);
t->initialize();
return static_cast<T*>(passes.emplace_back(std::move(t)).get());
}
GFXTexture* get_requested_texture(PassTextureType type) {
for(auto& pass : passes) {
auto texture = pass->get_requested_texture(type);
if(texture != nullptr)
return texture;
}
return nullptr;
}
GFXRenderPass* getOffscreenRenderPass() const {
return offscreenRenderPass;
}
GFXTexture* offscreenColorTexture = nullptr;
GFXTexture* offscreenDepthTexture = nullptr;
GFXTexture* viewportColorTexture = nullptr;
bool viewport_mode = false;
Extent viewport_extent;
bool gui_only_mode = false;
Extent get_extent() const {
return viewport_mode ? viewport_extent : extent;
}
Extent get_render_extent() const {
const auto extent = get_extent();
return {static_cast<uint32_t>(std::max(int(extent.width * render_options.render_scale), 1)),
static_cast<uint32_t>(std::max(int(extent.height * render_options.render_scale), 1))};
}
std::unique_ptr<ShadowPass> shadow_pass;
std::unique_ptr<SceneCapture> scene_capture;
/*
This is applied to most framebuffer pass projection-view matrices.
For example, under Metal the y is flipped because the framebuffer sample coordinates are flipped.
*/
Matrix4x4 correction_matrix;
GFXTexture* dummyTexture = nullptr;
GFXRenderPass* unormRenderPass = nullptr;
GFXPipeline* renderToUnormTexturePipeline = nullptr;
GFXRenderPass* viewportRenderPass = nullptr;
private:
void createDummyTexture();
void createOffscreenResources();
void createMeshPipeline();
void createPostPipeline();
void createFontPipeline();
void createSkyPipeline();
void createUIPipeline();
void createGaussianResources();
void createBRDF();
GFX* gfx = nullptr;
Extent extent;
ui::Screen* current_screen = nullptr;
// offscreen
GFXTexture* offscreenBackTexture = nullptr;
GFXFramebuffer* offscreenFramebuffer = nullptr;
GFXRenderPass* offscreenRenderPass = nullptr;
GFXFramebuffer* viewportFramebuffer = nullptr;
// mesh
GFXBuffer* sceneBuffer = nullptr;
// sky
GFXPipeline* skyPipeline = nullptr;
// post
GFXPipeline* postPipeline = nullptr;
GFXPipeline* renderToTexturePipeline = nullptr;
GFXPipeline* renderToViewportPipeline = nullptr;
// font
GFXTexture* fontTexture = nullptr;
GFXPipeline* textPipeline, *worldTextPipeline = nullptr;
int instanceAlignment = 0;
// brdf
GFXPipeline* brdfPipeline = nullptr;
GFXTexture* brdfTexture = nullptr;
GFXFramebuffer* brdfFramebuffer = nullptr;
GFXRenderPass* brdfRenderPass = nullptr;
// general ui
GFXPipeline* generalPipeline, *worldGeneralPipeline = nullptr;
std::unique_ptr<SMAAPass> smaaPass;
std::unique_ptr<GaussianHelper> gHelper;
std::unique_ptr<DoFPass> dofPass;
std::vector<std::unique_ptr<Pass>> passes;
};

View file

@ -0,0 +1,39 @@
#pragma once
#include "math.hpp"
class GFX;
class GFXCommandBuffer;
class GFXFramebuffer;
class GFXPipeline;
class GFXRenderPass;
class GFXTexture;
class GFXBuffer;
class Scene;
const int scene_cubemap_resolution = 1024;
const int irradiance_cubemap_resolution = 32;
class SceneCapture {
public:
SceneCapture(GFX* gfx);
void create_scene_resources(Scene& scene);
void render(GFXCommandBuffer* command_buffer, Scene* scene);
GFXPipeline* irradiancePipeline, *prefilterPipeline = nullptr, *skyPipeline = nullptr;
GFXRenderPass* renderPass = nullptr, *irradianceRenderPass = nullptr;
GFXTexture* environmentCube = nullptr;
GFXTexture* offscreenTexture = nullptr, *irradianceOffscreenTexture = nullptr, *prefilteredOffscreenTexture = nullptr;
GFXTexture* offscreenDepth = nullptr;
GFXFramebuffer* offscreenFramebuffer = nullptr, *irradianceFramebuffer = nullptr, *prefilteredFramebuffer = nullptr;
GFXBuffer* sceneBuffer = nullptr;
void createSkyResources();
void createIrradianceResources();
void createPrefilterResources();
};

View file

@ -0,0 +1,27 @@
#pragma once
#include <tuple>
#include "gfx.hpp"
class Material;
constexpr int position_buffer_index = 2;
constexpr int normal_buffer_index = 3;
constexpr int texcoord_buffer_index = 4;
constexpr int tangent_buffer_index = 5;
constexpr int bitangent_buffer_index = 6;
constexpr int bone_buffer_index = 7;
class ShaderCompiler {
public:
GFXPipeline* create_static_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only = false, bool cubemap = false);
GFXPipeline* create_skinned_pipeline(GFXGraphicsPipelineCreateInfo createInfo, bool positions_only = false);
// generates static and skinned versions of the pipeline provided
std::tuple<GFXPipeline*, GFXPipeline*> create_pipeline_permutations(GFXGraphicsPipelineCreateInfo& createInfo, bool positions_only = false);
std::string compile_material_fragment(Material& material, bool use_ibl = true);
};
static ShaderCompiler shader_compiler;

Some files were not shown because too many files have changed in this diff Show more