Updated ozz-animation to version 0.14.1.

RefactorUnifiedBlendTreeStateMachineHandling
Martin Felis 2023-04-15 00:07:29 +02:00
parent eb70c06c57
commit 72bcf8a21b
29 changed files with 1389 additions and 1071 deletions

View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1 +0,0 @@
ozz

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/ozz-animation.iml" filepath="$PROJECT_DIR$/.idea/ozz-animation.iml" />
</modules>
</component>
</project>

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,387 +0,0 @@
language: cpp
dist: xenial
jobs:
include:
# Emscripten cross compiling
#- os: linux
# compiler: gcc
# env:
# - env_build_emscripten=1
# mac os
# Default mac os clang build
- os: osx
osx_image: xcode10
compiler: clang
- os: osx
compiler: clang
- os: osx
compiler: clang
env:
- env_cmake_configuration=Release
# Xcode mac os clang build
- os: osx
compiler: clang
env :
- env_cmake_generator=Xcode
- os: osx
compiler: clang
env :
- env_cmake_generator=Xcode
- env_cmake_configuration=Release
# Linux ARM
- os: linux
compiler: clang
env:
- env_build_fbx=0
- env_cmake_configuration=Debug
arch:
- arm64
- os: linux
compiler: clang
env:
- env_build_fbx=0
- env_cmake_configuration=Release
arch:
- arm64
# Linux
# Default linux clang debug
- os: linux
compiler: clang
env:
- env_cmake_configuration=Debug
# Default linux release clang
- os: linux
compiler: clang
env:
- env_cmake_configuration=Release
# Specific configurations
- os: linux
compiler: clang
env:
- env_build_fbx=0
- os: linux
compiler: clang
env:
- env_build_gltf=0
- os: linux
compiler: clang
env:
- env_build_howtos=0
- os: linux
compiler: clang
env:
- env_build_samples=0
- os: linux
compiler: clang
env:
- env_build_tools=0
- os: linux
compiler: clang
env:
- env_build_postfix=0
- os: linux
compiler: clang
env:
- env_build_simd_ref=1
- os: linux
compiler: clang
env:
- env_build_tests=0
- os: linux
compiler: clang
env:
- env_build_fbx=0
- env_build_gltf=0
- env_build_samples=0
- os: linux
compiler: clang
env:
- env_build_fbx=0
- env_build_gltf=0
- env_build_tests=0
- os: linux
compiler: clang
env:
- env_build_data=0
- env_build_fbx=0
- env_build_gltf=0
- env_build_samples=0
- env_build_tests=0
- os: linux
compiler: clang
env:
- env_build_data=0
- os: linux
compiler: clang
env:
- env_src_root="../test/sub/"
# Default linux gcc debug
- os: linux
compiler: gcc
env:
- env_cmake_configuration=Debug
# Default linux gcc release
- os: linux
compiler: gcc
env:
- env_cmake_configuration=Release
# Newer gcc
- os: linux
compiler: gcc-4.9
env:
- env_cmake_cxx_compiler=g++-4.9
- env_cmake_c_compiler=gcc-4.9
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.9
- os: linux
compiler: gcc-5
env:
- env_cmake_cxx_compiler=g++-5
- env_cmake_c_compiler=gcc-5
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-5
- os: linux
compiler: gcc-6
env:
- env_cmake_cxx_compiler=g++-6
- env_cmake_c_compiler=gcc-6
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-6
- os: linux
compiler: gcc-7
env:
- env_cmake_cxx_compiler=g++-7
- env_cmake_c_compiler=gcc-7
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-7
- os: linux
compiler: gcc-8
env:
- env_cmake_cxx_compiler=g++-8
- env_cmake_c_compiler=gcc-8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-8
# Newer clang
- os: linux
compiler: clang-3.9
env:
- env_cmake_cxx_compiler=clang++-3.9
- env_cmake_c_compiler=clang-3.9
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-3.9
packages:
- clang-3.9
- os: linux
compiler: clang-4.0
env:
- env_cmake_cxx_compiler=clang++-4.0
- env_cmake_c_compiler=clang-4.0
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-4.0
packages:
- clang-4.0
- os: linux
compiler: clang-5.0
env:
- env_cmake_cxx_compiler=clang++-5.0
- env_cmake_c_compiler=clang-5.0
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-5.0
packages:
- clang-5.0
- os: linux
compiler: clang-6.0
env:
- env_cmake_cxx_compiler=clang++-6.0
- env_cmake_c_compiler=clang-6.0
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-6.0
packages:
- clang-6.0
- os: linux
compiler: clang-7
env:
- env_cmake_cxx_compiler=clang++-7
- env_cmake_c_compiler=clang-7
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-7
packages:
- clang-7
- os: linux
compiler: clang-8
env:
- env_cmake_cxx_compiler=clang++-8
- env_cmake_c_compiler=clang-8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-8
packages:
- clang-8
# older fbx sdk
- os: linux
compiler: gcc
env:
- fbx_download=http://download.autodesk.com/us/fbx/2017/2017.1/fbx20171_fbxsdk_linux.tar.gz
- os: osx
compiler: clang
env:
- fbx_download=http://download.autodesk.com/us/fbx/2017/2017.1/fbx20171_fbxsdk_clang_mac.pkg.tgz
before_install:
- echo before_install----------------------------------------------------------
install:
- echo install-----------------------------------------------------------------
# Download and install mesa dev
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev;
fi
# Download and install fbx sdk
# "|| true" because 2019 sdk would return code 130 when reading the readme
# chmod because fbx2019 will install with 700
- if [[ $TRAVIS_OS_NAME == "linux" ]]; then
if [ -z "$fbx_download" ]; then
FBX_DOWNLOAD=${fbx_download:-"http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20192/fbx20192_fbxsdk_linux.tar.gz"};
else
FBX_DOWNLOAD="$fbx_download";
fi;
mkdir fbx;
cd fbx;
sudo wget $FBX_DOWNLOAD -O fbx.tar.gz;
sudo tar -xf "fbx.tar.gz";
(yes yes | sudo ./*_fbxsdk_linux /usr/local) || true;
sudo chmod -R 755 /usr/local/lib;
sudo chmod -R 755 /usr/local/include;
cd ..;
fi
- if [[ $TRAVIS_OS_NAME == "osx" ]]; then
if [ -z "$fbx_download" ]; then
FBX_DOWNLOAD=${fbx_download:-"http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20192/fbx20192_fbxsdk_clang_mac.pkg.tgz"};
else
FBX_DOWNLOAD="$fbx_download";
fi;
mkdir fbx;
cd fbx;
sudo wget $FBX_DOWNLOAD -O fbx.tgz;
sudo tar -xf "fbx.tgz";
sudo installer -pkg *_fbxsdk_clang_macos.pkg -target /;
cd ..;
fi
# Download and install emscripten sdk
- if [[ $env_build_emscripten ]]; then
sudo apt-get install software-properties-common -y;
sudo add-apt-repository ppa:george-edison55/cmake-3.x -y;
sudo apt-get update;
sudo apt-get install --only-upgrade cmake -y;
git clone https://github.com/juj/emsdk.git;
cd emsdk;
sudo ./emsdk update-tags;
sudo ./emsdk install sdk-nightly-latest-64bit;
sudo ./emsdk activate --embedded sdk-nightly-latest-64bit;
source ./emsdk_env.sh;
cd ..;
fi
before_script:
- echo before_script-----------------------------------------------------------
# Setup default environment variables
- if [[ -z $env_src_root ]]; then export env_src_root=".."; fi
- if [[ -z $env_build_fbx ]]; then export env_build_fbx=1; fi
- if [[ -z $env_build_gltf ]]; then export env_build_gltf=1; fi
- if [[ -z $env_build_data ]]; then export env_build_data=1; fi
- if [[ -z $env_build_howtos ]]; then export env_build_howtos=1; fi
- if [[ -z $env_build_tools ]]; then export env_build_tools=1; fi
- if [[ -z $env_build_postfix ]]; then export env_build_postfix=1; fi
- if [[ -z $env_build_samples ]]; then export env_build_samples=1; fi
- if [[ -z $env_build_simd_ref ]]; then export env_build_simd_ref=0; fi
- if [[ -z $env_build_tests ]]; then export env_build_tests=1; fi
- if [[ -z $env_cmake_configuration ]]; then export env_cmake_configuration=Debug; fi
- if [[ -z $env_cmake_cxx_compiler ]]; then export env_cmake_cxx_compiler=$CXX; fi
- if [[ -z $env_cmake_c_compiler ]]; then export env_cmake_c_compiler=$CC; fi
- if [[ -z $env_cmake_generator ]]; then export env_cmake_generator="Unix Makefiles"; fi
- if [[ $env_cmake_generator == "Unix Makefiles" ]]; then export env_cmake_generator_specific="-j2"; fi
- if [[ $env_cmake_generator == "Unix Makefiles" ]]; then export env_ctest_generator_specific="-j16"; fi
- if [[ $EMSCRIPTEN ]]; then env_cmake_toolchain="-DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN/cmake/Modules/Platform/Emscripten.cmake"; fi
# Display cmake version
- cmake --version
script:
- echo script------------------------------------------------------------------
# Configure build
- mkdir build
- cd build
- echo $env_cmake_toolchain
- cmake -G "$env_cmake_generator" $env_cmake_toolchain -DCMAKE_CXX_COMPILER=$env_cmake_cxx_compiler -DCMAKE_C_COMPILER=$env_cmake_c_compiler -DCMAKE_BUILD_TYPE=$env_cmake_configuration -Dozz_build_fbx=$env_build_fbx -Dozz_build_data=$env_build_data -Dozz_build_howtos=$env_build_howtos -Dozz_build_samples=$env_build_samples -Dozz_build_postfix=$env_build_postfix -Dozz_build_tools=$env_build_tools -Dozz_build_simd_ref=$env_build_simd_ref -Dozz_build_tests=$env_build_tests $env_src_root
# Build
- cmake --build ./ --config $env_cmake_configuration --use-stderr -- $env_cmake_generator_specific
# Test
- ctest --build-config $env_cmake_configuration --output-on-failure -- $env_ctest_generator_specific
after_success:
- echo after_success-----------------------------------------------------------
after_failure:
- echo after_failure-----------------------------------------------------------
before_deploy:
#- echo before_deploy-----------------------------------------------------------
deploy:
#- echo deploy------------------------------------------------------------------
after_deploy:
#- echo after_deploy------------------------------------------------------------
after_script:
#- echo after_script------------------------------------------------------------

View File

@ -14,3 +14,4 @@ The following authors have all licensed their contributions to ozz-animation und
- Kota Iguchi <developer@infosia.co.jp>
- Mikołaj Siedlarek <mikolaj@siedlarek.net>
- Paul Gruenbacher <pgruenbacher@gmail.com>
- Christophe Meyer <christophe.meyer.pro@gmail.com>

View File

@ -1,3 +1,17 @@
Release version 0.14.1
----------------------
* Samples
- Allows reusing sample framework outside of sample directory.
- #154 Exposes swap interval
* Library
- #153 Fixes deprecated implicit copy warning.
- #141 Removes non-ASCII characters in source codes.
* Build pipeline
- Exposes ozz cmake configuration variables to PARENT_SCOPE, so it can be used/changed by an external project.
Release version 0.14.0
----------------------

View File

@ -3,10 +3,13 @@ cmake_minimum_required (VERSION 3.3)
# Defines the project's name
project(ozz)
# Check if project is top level or a sub project
get_directory_property(is_sub_project PARENT_DIRECTORY)
# Current version
set(OZZ_VERSION_MAJOR 0)
set(OZZ_VERSION_MINOR 14)
set(OZZ_VERSION_PATCH 0)
set(OZZ_VERSION_PATCH 1)
set(OZZ_VERSION ${OZZ_VERSION_MAJOR}.${OZZ_VERSION_MINOR}.${OZZ_VERSION_PATCH})
# Add project build options
@ -27,6 +30,9 @@ if(WIN32 AND BUILD_SHARED_LIBS AND NOT ozz_build_msvc_rt_dll)
message("Forcing ozz_build_msvc_rt_dll to ON as ozz is being built as dll (BUILD_SHARED_LIBS is ON).")
set(ozz_build_msvc_rt_dll ON)
endif()
if(is_sub_project)
set(ozz_build_msvc_rt_dll ${ozz_build_msvc_rt_dll} PARENT_SCOPE)
endif()
# Include ozz cmake parameters and scripts
include(CheckCXXCompilerFlag)
@ -63,12 +69,18 @@ else()
# Disables fbx if tools are disabled
set(ozz_build_fbx OFF)
endif()
if(is_sub_project)
set(ozz_build_fbx ${ozz_build_fbx} PARENT_SCOPE)
endif()
# gltf
if(ozz_build_tools AND ozz_build_gltf)
else()
set(ozz_build_gltf OFF)
endif()
if(is_sub_project)
set(ozz_build_gltf ${ozz_build_gltf} PARENT_SCOPE)
endif()
# Enables unit tests.
if(ozz_build_tests)
@ -115,7 +127,6 @@ if(ozz_build_tests AND NOT EMSCRIPTEN)
add_subdirectory(test)
endif()
install(FILES
${PROJECT_SOURCE_DIR}/CHANGES.md
${PROJECT_SOURCE_DIR}/LICENSE.md

View File

@ -1,88 +0,0 @@
version: '{build}'
image: Visual Studio 2019
environment:
matrix:
# Comilers matrix
- env_cmake_generator: "Visual Studio 14 2015"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
env_cmake_configuration: Debug
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2015_win.exe"
- env_cmake_generator: "Visual Studio 14 2015 Win64"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015
env_cmake_configuration: Debug
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2015_win.exe"
- env_cmake_generator: "Visual Studio 15 2017"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
env_cmake_configuration: Debug
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 15 2017 Win64"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
env_cmake_configuration: Debug
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 15 2017"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
env_cmake_configuration: Release
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 15 2017 Win64"
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
env_cmake_configuration: Release
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Debug
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Release
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Release
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
env_build_msvc_rt_dll: "1"
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Release
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
env_build_msvc_rt_dll: "0"
# No fbx sdk
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Release
# Use ozz as a sub project (should set msvc rt dll for ozz as this is the default in cmake)
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Release
env_src_root: "../test/sub/"
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
- env_cmake_generator: "Visual Studio 16 2019"
env_cmake_configuration: Debug
env_src_root: "../test/sub/"
env_fbxsdk_path: "http://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20195/fbx20195_fbxsdk_vs2017_win.exe"
install:
# Setup Fbx sdk
- ps: >-
if ($Env:env_fbxsdk_path) {
md fbx
cd fbx
Start-FileDownload $Env:env_fbxsdk_path "fbxsdk.exe"
Start-Process -FilePath "fbxsdk.exe" /S -Wait
cd ..
}
# Fixes up env_src_root if it isn't defined
- ps: >-
if (!$Env:env_src_root) {
$Env:env_src_root=".."
}
# Fixes up env_build_msvc_rt_dll if it isn't defined
- ps: >-
if (!$Env:env_build_msvc_rt_dll) {
$Env:env_build_msvc_rt_dll="1"
}
build_script:
- cmake --version
- mkdir build
- cd build
- cmake -G "%env_cmake_generator%" -Dozz_build_tests=1 -Dozz_build_data=1 -Dozz_build_msvc_rt_dll=%env_build_msvc_rt_dll% -DCMAKE_BUILD_TYPE=%env_cmake_configuration% %env_src_root%
- cmake --build ./ --config %env_cmake_configuration%
test_script:
- ctest --build-config %env_cmake_configuration%

View File

@ -7917,7 +7917,7 @@ namespace edit_distance {
// Returns the optimal edits to go from 'left' to 'right'.
// All edits cost the same, with replace having lower priority than
// add/remove.
// Simple implementation of the WagnerFischer algorithm.
// Simple implementation of the Wagner-Fischer algorithm.
// See http://en.wikipedia.org/wiki/Wagner-Fischer_algorithm
enum EditType { kMatch, kAdd, kRemove, kReplace };
GTEST_API_ std::vector<EditType> CalculateOptimalEdits(

View File

@ -61,6 +61,9 @@ struct span {
// elements.
span(_Ty* _begin, size_t _size) : data_(_begin), size_(_size) {}
// Copy constructor.
span(const span& _other) = default;
// Copy operator.
void operator=(const span& _other) {
data_ = _other.data_;

Binary file not shown.

View File

@ -36,6 +36,10 @@ target_link_libraries(sample_framework
ozz_geometry
ozz_animation_offline
ozz_options)
target_include_directories(sample_framework PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/samples>
$<INSTALL_INTERFACE:$<INSTALL_PREFIX>/samples>)
if(TARGET BUILD_DATA_SAMPLE)
add_dependencies(sample_framework BUILD_DATA_SAMPLE)

View File

@ -98,6 +98,8 @@ Application::Application()
time_(0.f),
last_idle_time_(0.),
show_help_(false),
vertical_sync_(true),
swap_interval_(1),
show_grid_(true),
show_axes_(true),
capture_video_(false),
@ -209,7 +211,7 @@ int Application::Run(int _argc, const char** _argv, const char* _version,
#endif // EMSCRIPTEN
// Setup the window and installs callbacks.
glfwSwapInterval(1); // Enables vertical sync by default.
glfwSwapInterval(vertical_sync_ ? swap_interval_ : 0);
glfwSetWindowSizeCallback(&ResizeCbk);
glfwSetWindowCloseCallback(&CloseCbk);
@ -611,10 +613,14 @@ bool Application::FrameworkGui() {
GL(Disable(GL_MULTISAMPLE));
}
}
// Vertical sync
static bool vertical_sync_ = true; // On by default.
if (im_gui->DoCheckBox("Vertical sync", &vertical_sync_, true)) {
glfwSwapInterval(vertical_sync_ ? 1 : 0);
// Vertical sync & swap interval
bool changed = im_gui->DoCheckBox("Vertical sync", &vertical_sync_);
char szLabel[64];
std::sprintf(szLabel, "Swap interval: %d", swap_interval_);
changed |=
im_gui->DoSlider(szLabel, 1, 4, &swap_interval_, 1.f, vertical_sync_);
if (changed) {
glfwSwapInterval(vertical_sync_ ? swap_interval_ : 0);
}
im_gui->DoCheckBox("Show grid", &show_grid_, true);

View File

@ -216,6 +216,9 @@ class Application {
// Set to true to display help.
bool show_help_;
bool vertical_sync_; // On by default.
int swap_interval_;
// Grid display settings.
bool show_grid_;
bool show_axes_;

View File

@ -32,6 +32,7 @@
#include "framework/imgui.h"
#include "framework/mesh.h"
#include "ozz/animation/offline/raw_animation.h"
#include "ozz/animation/offline/raw_skeleton.h"
#include "ozz/animation/runtime/animation.h"
#include "ozz/animation/runtime/local_to_model_job.h"
@ -326,6 +327,30 @@ bool LoadAnimation(const char* _filename,
return true;
}
bool LoadRawAnimation(const char* _filename,
ozz::animation::offline::RawAnimation* _animation) {
assert(_filename && _animation);
ozz::log::Out() << "Loading raw animation archive: " << _filename << "."
<< std::endl;
ozz::io::File file(_filename, "rb");
if (!file.opened()) {
ozz::log::Err() << "Failed to open raw animation file " << _filename << "."
<< std::endl;
return false;
}
ozz::io::IArchive archive(&file);
if (!archive.TestTag<ozz::animation::offline::RawAnimation>()) {
ozz::log::Err() << "Failed to load raw animation instance from file "
<< _filename << "." << std::endl;
return false;
}
// Once the tag is validated, reading cannot fail.
archive >> *_animation;
return true;
}
namespace {
template <typename _Track>
bool LoadTrackImpl(const char* _filename, _Track* _track) {
@ -411,7 +436,7 @@ bool LoadMeshes(const char* _filename,
}
namespace {
// MollerTrumbore intersection algorithm
// Moller-Trumbore intersection algorithm
// https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm
bool RayIntersectsTriangle(const ozz::math::Float3& _ray_origin,
const ozz::math::Float3& _ray_direction,

View File

@ -167,6 +167,14 @@ bool LoadSkeleton(const char* _filename, ozz::animation::Skeleton* _skeleton);
bool LoadAnimation(const char* _filename,
ozz::animation::Animation* _animation);
// Loads a raw animation from an ozz archive file named _filename.
// This function will fail and return false if the file cannot be opened or if
// it is not a valid ozz animation archive. A valid animation archive can be
// produced with ozz tools (fbx2ozz) or using ozz animation serialization API.
// _filename and _animation must be non-nullptr.
bool LoadRawAnimation(const char* _filename,
ozz::animation::offline::RawAnimation* _animation);
// Loads a float track from an ozz archive file named _filename.
// This function will fail and return false if the file cannot be opened or if
// it is not a valid ozz float track archive. A valid float track archive can be

View File

@ -41,7 +41,7 @@ namespace ozz {
namespace animation {
namespace offline {
// Decimation algorithm based on RamerDouglasPeucker.
// Decimation algorithm based on Ramer-Douglas-Peucker.
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// _Track must have std::vector interface.
// Adapter must have the following interface:

View File

@ -184,7 +184,7 @@ bool SoftenTarget(const IKTwoBoneJob& _job, const IKConstantSetup& _setup,
// The maximum distance we can reach is the soften bone chain length: da
// (stored in !x). The minimum distance we can reach is the absolute value of
// the difference of the 2 bone lengths, |d1d2| (stored in z). x is 0 and z
// the difference of the 2 bone lengths, |d1-d2| (stored in z). x is 0 and z
// is 1, yw are untested.
return (comp_mask & 0x5) == 0x4;
}

View File

@ -645,10 +645,9 @@ class is_to_be_removed {
public:
explicit is_to_be_removed(int _which) : which_(_which) {}
bool operator()(typename _List::const_reference) { return which_-- == 0; }
void operator=(const is_to_be_removed&) = delete;
private:
void operator=(const is_to_be_removed&);
int which_;
};

View File

@ -1,5 +1,691 @@
## Updates
- **20-Feb-2023**: sokol_gfx.h has a new set of functions to get a 'best-effort'
desc struct with the creation parameters of a specific resource object:
```c
sg_buffer_desc sg_query_buffer_desc(sg_buffer buf);
sg_image_desc sg_query_image_desc(sg_image img);
sg_shader_desc sg_query_shader_desc(sg_shader shd);
sg_pipeline_desc sg_query_pipeline_desc(sg_pipeline pip);
sg_pass_desc sg_query_pass_desc(sg_pass pass);
```
The returned structs will *not* be an exact copy of the desc struct that
was used for creation the resource object, instead:
- references to external data (like buffer and image content or
shader sources) will be zeroed
- any attributes that have not been kept around internally after
creation will be zeroed (the ```sg_shader_desc``` struct is most
affected by this, the other structs are fairly complete).
Calling the functions with an invalid or dangling resource handle
will return a completely zeroed struct (thus it may make sense
to first check the resource state via ```sg_query_*_state()```)
Nevertheless, those functions may be useful to get a partially filled out
'creation blueprint' for creating similar resources without the need
to keep and pass around the original desc structs.
>MINOR BREAKING CHANGE: the struct members ```sg_image_info.width``` and
```sg_image_info.height``` have been removed, this information is now
returned by ```sg_query_image_desc()```.
PR: https://github.com/floooh/sokol/pull/796, fixes: https://github.com/floooh/sokol/issues/568
- **17-Feb-2023**: sokol_app.h on macOS now has a proper fix for the problem
that macOS doesn't send key-up events while the Cmd key is held down.
Previously this was handled through a workaround of immediately sending a
key-up event after its key-down event if the Cmd key is currently held down
to prevent a 'stuck key'. The proper fix is now to install an "event monitor"
callback (many thanks to GLFW for finding and implementing the solution).
Unfortunately there's no such solution for the Emscripten code path, which
also don't send a key-up event while Cmd is pressed on macOS (the workaround
there to send a key-up event right on key-down while Cmd is held down to
prevent a stuck key is still in place) For more details, see:
https://github.com/floooh/sokol/issues/794
- **15-Feb-2023**: A fix in the sokol_gfx.h GL backend: due to a bug in the
state cache, the GL backend could only bind a total of
SG_MAX_SHADERSTAGE_IMAGES (= 12) when it actually should be twice that amount
(12 per shader stage). Note however that the total amount of texture bindings
is still internally limited by the GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS
runtime variable (~~currently this is not exposed in sg_limits though~~). Many
thanks to @allcreater for PR https://github.com/floooh/sokol/pull/787.
PS: sg_limits now exposes GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS as
```sg_limits.gl_max_combined_texture_image_units```, and the
value can also be inspected via the debug UI in sokol_gfx_imgui.h.
- **13-Feb-2023**: The way logging works has been completely revamped in
the sokol headers. UWP support has been removed from sokol_audio.h
and sokol_app.h (this also means that the sokol headers no longer contain
any C++ code).
**REQUIRED ACTION**: Since the sokol headers are now completely silent
without a logging callback (explanation below), it is highly recommended
to use the standard logging callback provided by the new header ```sokol_log.h```.
For instance for sokol_gfx.h it looks like this:
```c
#include "sokol_log.h"
//...
sg_setup(&(sg_desc){
//...
.logger.func = slog_func,
});
```
All sokol samples have been updated to use sokol_log.h for logging.
The former logging callback is now a combined
logging- and error-reporting callback, and more information is available
to the logging function:
- a 'tag string' which identifies the sokol headers, this string
is identical with the API prefix (e.g. "sg" for sokol_gfx.h,
"sapp" for sokol_app.h etc...)
- a numeric log level: 0=panic, 1=error, 2=warning, 3=info
- a numeric 'log item id' (think of it as error code, but since
not only errors are reported I called it a log item id)
- a human readable error message
- a source file line number where the log item was reported
- the file path of the sokol header
Log level ```panic``` is special in that it terminates execution inside
the log function. When a sokol header issues a panic log message, it means
that the problem is so big that execution can not continue. By default,
the sokol headers and the standard log function in sokol_log.h call
```abort()``` when a panic log message is issued.
In debug mode (NDEBUG not defined, or SOKOL_DEBUG defined), a log message
(in this case from sokol_spine.h) will look like this:
```
[sspine][error][id:12] /Users/floh/projects/sokol/util/sokol_spine.h:3472:0:
SKELETON_DESC_NO_ATLAS: no atlas object provided in sspine_skeleton_desc.atlas
```
The information can be 'parsed' like this:
- ```[sspine]```: it's a message from sokol_spine.h
- ```[error]```: it's an error
- ```[id:12]```: the numeric log item id (associated with ```SKELETON_DESC_NO_ATLAS``` below)
- source file path and line number in a compiler-specific format - in some IDEs and terminals
this is a clickable link
- the line below is the human readable log item id and message
In release mode (NDEBUG is defined and SOKOL_DEBUG is not defined), log messages
are drastically reduced (the reason is to not bloat the executable with all the extra string data):
```
[sspine][error][id:12][line:3472]
```
...this reduced information still gives all the necessary information to identify the location and type of error.
A custom logging function must adhere to a few rules:
- must be re-entrant because it might be called from different threads
- must treat **all** provided string pointers as optional (can be null)
- don't store the string pointers, copy the string data instead
- must not return for log level panic
A new header ```sokol_log.h``` has been added to provide a standard logging callback implementation
which provides logging output on all platforms to stderr and/or platform specific logging
facilities. ```sokol_log.h``` only uses fputs() and platform specific logging function instead
of fprintf() to preverse some executable size.
**QUESTION**: Why are the sokol headers now silent, unless a logging callback is installed?
This is mainly because a standard logging function which does something meaningful on all
platforms (including Windows and Android) isn't trivial. E.g. printing to stderr is not
enough. It's better to move that stuff into a centralized place in a separate header,
but since the core sokol headers must not (statically) depend on other sokol headers
the only solution that made sense was to provide a standard logging function which must
be 'registered' as a callback.
- **26-Jan-2023**: Work on SRGB support in sokol_gfx.h has started, but
this requires more effort to be really usable. For now, only a new
pixel format has been added: SG_PIXELFORMAT_SRGB8A8 (see https://github.com/floooh/sokol/pull/758,
many thanks to @allcreater). The sokol-gfx GL backend has a temporary
workaround to align behaviour with D3D11 and Metal: automatic SRGB conversion
is enabled for offscreen render passes, but disabled for the default
framebuffer. A proper fix will require separate work on sokol_app.h to
support an SRGB default framebuffer and communicate to sokol-gfx
whether the default framebuffer is SRGB enabled or not.
- **24-Jan-2023**: sokol_gfx.h Metal: A minor inconsistency has been fixed in
the validation layer and an assert for the function ```sg_apply_uniforms()```
which checks the size of the incoming data against the uniform block size.
The validation layer and Metal backend did a ```<=``` test while the D3D11
and GL backends checked for an exact size match. Both the validation layer
and the Metal backend now also check for an exact match. Thanks to @nmr8acme
for noticing the issue and providing a PR! (https://github.com/floooh/sokol/pull/776)
- **23-Jan-2023**: A couple more sokol_audio.h updates:
- an AAudio backend has been added for Android, and made the default. This
means you now need to link with ```aaudio``` instead of ```OpenSLES``` when
using sokol_audio.h on Android. The OpenSLES backend code still exists (for
now), but must be explicitly selected by compiling the sokol_audio.h
implementation with the define ```SAUDIO_ANDROID_SLES``` (e.g. there is
no runtime fallback from AAudio to OpenSLES). AAudio is fully supported
since Android 8.1. Many thanks to @oviano for the initial AAudio PR
(https://github.com/floooh/sokol/pull/484)
- in the WebAudio backend, WebAudio is now properly activated on the first
input action again on Chrome for Android (at some point activating WebAudio
via a ```touchstart``` event stopped working and had to be moved to the
```touchend``` event, see https://github.com/floooh/sokol/issues/701)
- audio backend initialization on iOS and macOS is now a bit more fault-tolerant,
errors during initialization now properly set sokol_audio.h to 'silent mode'
instead of asserting (or in release mode ignoring the error)
- ...and some minor general code cleanup things in sokol_audio.h: backend-specific
functions now generally have a matching prefix (like ```_saudio_alsa_...()```)
for better searchability
- **16-Jan-2023**:
- sokol_audio.h android: https://github.com/floooh/sokol/pull/747 has been merged
which adds a couple more error checks at OpenSLES startup.
- sokol_gfx.h: support for half-float vertex formats has been added via
PR https://github.com/floooh/sokol/pull/745
- sokol_imgui.h: fixes for Dear ImGui 1.89 deprecations (via PR https://github.com/floooh/sokol/pull/761)
- **15-Jan-2023**: two bugfixes in sokol_app.h and sokol_gfx.h:
- sokol_app.h x11: Mouse button events now always return valid mouse
coordinates, also when no mouse movement happened yet
(fixes https://github.com/floooh/sokol/issues/770)
- sokol_gfx.h gl: The GL context is now configured with
GL_UNPACK_ALIGNMENT = 1, this should bring texture creation and updating
behaviour in line with the other backends for tightly packed texture
data that doesn't have a row-pitch with a multiple of 4
(fixes https://github.com/floooh/sokol/issues/767)
- **14-Jan-2023**: sokol_app.h x11: a drag'n'drop related bugfix, the
XdndFinished reply event was sent with the wrong window handle which
confused some apps where the drag operation originated
(see https://github.com/floooh/sokol/pull/765#issuecomment-1382750611)
- **16-Dec-2022**: In the sokol_gfx.h Metal backend: A fix for a Metal
validation layer error which I just discovered yesterday (seems to be new in
macOS 13). When the validation layer is active, and the application window
becomes fully obscured, the validation layer throws an error after a short
time (for details see: https://github.com/floooh/sokol/issues/762).
The reason appears to be that sokol_gfx.h creates a command buffer with
'unretained references' (e.g. the command buffer doesn't manage the
lifetime of resources used by the commands stored in the buffer). This
seems to clash with MTKView's and/or CAMetalLayer's expectations. I fixed
this now by creating a second command buffer with 'retained references',
which only holds the ```presentDrawable``` command. That way, regular
draw commands don't have the refcounting overhead (because they're stored
in an unretained-references cmdbuffer), while the drawable surface is
still properly lifetime managed (because it's used in a separate command
buffer with retained references).
- **15-Dec-2022**: A small but important update in sokol_imgui.h which fixes
touch input handling on mobile devices. Many thanks to github user @Xadiant
for the bug investigation and [PR](https://github.com/floooh/sokol/pull/760).
- **25-Nov-2022**: Some code cleanup around resource creation and destruction in sokol_gfx.h:
- It's now safe to call the destroy, uninit and dealloc functions in any
resource state, in general, the functions will do the right thing without
assertions getting in the way (there are however new log warnings in some
cases though, such as attempting to call an ```sg_dealloc_*()``` function on
a resource object that's not in ALLOC state)
- A related **minor breaking change**: the ```sg_uninit_*()``` functions now return
void instead of bool, this is because ```sg_dealloc_*()``` no longer asserts
when called in the wrong resource state
- Related internal code cleanup in the backend-agnostic resource creation
and cleanup code, better or more consistent function names, etc...
- The validation layer can now be disabled in debug mode with a runtime
flag during setup: ```sg_desc.disable_validation```. This is mainly useful
for test code.
- Creating a pass object with invalid image objects now no longer asserts,
but instead results in a pass object in FAILED state. In debug mode,
the validation layer will still stop at this problem though (it's mostly
an 'undefined API behaviour' fix in release mode).
- Calling ```sg_shutdown()``` with existing resources in ALLOC state will
no longer print a log message about an 'active context mismatch'.
- A new header documentation blurb about the two-step resource creation
and destruction functions (search for RESOURCE CREATION AND DESTRUCTION IN DETAIL)
- **16-Nov-2022**: Render layer support has been added to sokol_debugtext.h,
same general changes as in sokol_gl.h with two new functions:
sdtx_layer(layer_id) to select the layer to record text into, and
sdtx_draw_layer(layer_id) to draw the recorded text in that layer inside a
sokol-gfx render pass. The new sample [debugtext-layers-sapp](https://floooh.github.io/sokol-html5/debugtext-layers-sapp) demonstrates the feature together with
sokol-gl.
- **11-Nov-2022**: sokol_gl.h has 2 new public API functions which enable
layered rendering: sgl_layer(), sgl_draw_layer() (technically it's three
functions: there's also sgl_context_draw_layer(), but that's just a variant of
sgl_draw_layer()). This allows to 'interleave' sokol-gl rendering
with other render operations. The [spine-layers-sapp](https://floooh.github.io/sokol-html5/spine-layers-sapp.html)
sample has been updated to use multiple sokol-gl layers.
- **09-Nov-2022**: sokol_gfx.h now allows to add 'commit listeners', these
are callback functions which are called from inside sg_commit(). This is
mainly useful for libraries which build on top of sokol-gfx to be notified
about the start/end point of a frame, which in turn may simplify the public
API, or the internal implementation, because the library no longer needs to
'guess' when a new frame starts.
For more details, search for 'COMMIT LISTENERS' in the sokol_gfx.h header.
This also results in a minor breaking change in sokol_spine.h: The function
```sspine_new_frame()``` has been removed and replaced with an internal commit
listener.
Likewise, sokol_gl.h now uses a commit listener in the implementation, but
without changing the public API (the feature will be important for an upcoming
sokol-gl feature to support rendering layers, and for this a 'new-frame-function'
would have been needed).
- **05-Nov-2022** A breaking change in sokol_fetch.h, and a minor change in
sokol_app.h which should only break for very few users:
- An ```sfetch_range_t``` ptr/size pair struct has been added to sokol_fetch.h,
and discrete ptr/size pairs have been replaced with sfetch_range_t
items. This affects the structs ```sfetch_request_t``` and ```sfetch_response_t```,
and the function ```sfetch_bind_buffer()```.
- The required changes in ```sfetch_response_t``` might be a bit non-obviois: To
access the fetched data, previous ```.buffer_ptr``` and ```.fetched_size```
was used. The fetched data is now accessible through an ```sfetch_range_t data```
item (```data.ptr``` and ```data.size```). The old ```.fetched_offset``` item
has been renamed to ```.data_offset``` to better conform with the new naming.
- The last two occurrences of discrete ptr/size pairs in sokol_app.h now have also
been replaced with ```sapp_range_t``` items, this only affects the structs
```sapp_html5_fetch_request``` and ```sapp_html5_fetch_response```.
- **03-Nov-2022** The language bindings generation has been updated for Zig 0.10.0,
and clang-14 (there was a minor change in the JSON ast-dump format).
Many thanks to github user @kcbanner for the Zig PR!
- **02-Nov-2022** A new header sokol_spine.h (in the util dir), this is a
renderer and 'handle wrapper' around the spine-c runtime (Spine is a popular 2D
character anim system: http://esotericsoftware.com/). This turned out a much bigger
rabbit-hole than I initially expected, but the effort is justified by being a
experimentation testbed for a couple of things I want to add to other sokol
headers (for instance cleaned up handle pool code, a new logging- and error-reporting
system, render layers which will be useful for sokol_gl.h and sokol_debugtext.h).
- **22-Oct-2022** All sokol headers now allow to override logging with a
callback function (installed in the setup call) instead of defining a SOKOL_LOG
macro. Overriding SOKOL_LOG still works as default fallback, but this is no
longer documented, consider this deprecated. Many thanks to github user
@Manuzor for the PR (see https://github.com/floooh/sokol/pull/721 for details)
- **21-Oct-2022** RGB9E5 pixel format support in sokol_gfx.h and a GLES2 related
bugfix in the sokol_app.h Android backend:
- sokol_gfx.h now supports RGB9E5 textures (3*9 bit RGB + 5 bit shared exponent),
this works in all backends except GLES2 and WebGL1 (use ```sg_query_pixelformat()```
to check for runtime support). Many thanks to github user @allcreater for the PR!
- a bugfix in the sokol_app.h Android backend: when forcing a GLES2 context via
sapp_desc.gl_force_gles2, the Android backend correctly created a GLES2 context,
but then didn't communicate this through the function ```sapp_gles2()``` (which
still returned false in this case). This caused the sokol_gfx.h GL backend to
use the GLES3 code path instead GLES2 (which surprisingly seemed to have worked
fine, at least for the sokol samples which force GLES2).
- **19-Oct-2022** Some fixes in the embedded Javascript code blocks (via EM_JS)
in sokol_app.h, sokol_args.h, sokol_audio.h and sokol_fetch.h:
- the JS code has been 'modernized' (e.g. const and let instead of var,
```() => { ... }``` instead of ```function () { ... }``` for callbacks)
- false positives in the Closure static analysis have been suppressed
via inline hints
- **16-Oct-2022** The Odin bindings generator and the generated bindings have
been simplified (the Odin binding now don't have separate wrapper functions).
Requires the latest Odin release. Also note: On M1 Macs I'm currently seeing
what looks like an ABI problem (in functions which pass color values to the C
side as uint8_t, the colors come out wrong). This also happened with the
previous binding version, so it looks like a regression in Odin. Might be
related to this recent bugfix (which I haven't tested yet):
https://github.com/odin-lang/Odin/issues/2121 Many thanks to @thePHTest for the
PR! (https://github.com/floooh/sokol/pull/719)
- **15-Oct-2022**
- fixes for Emscripten 3.1.24: the sokol headers now use the new
**EM_JS_DEPS()** macro to declare 'indirect dependencies on JS library functions'.
This is a (much more robust) follow-up fix to the Emscripten related fixes from 10-Sep-2022.
The new Emscripten SDK also displays a couple of Javascript "static analyzer" warnings
by the Closure compiler (used in release mode to optimize and minify the generated
JS code). I fixed a couple of those warnings, but some warnings persist (all of them
false positives). Not sure yet if these can be fixed or need to be suppressed, but
that's for another time.
- the webkitAudioContext() fallback in sokol_audio.h's Emscripten backend
has been removed (only AudioContext is supported now), the fallback also
triggered a Closure warning, so it probably never worked as intended anyway.
- I also had to undo an older workaround in sokol_app.h on iOS (https://github.com/floooh/sokol/issues/645)
because this is now triggering a Metal validation layer error (https://github.com/floooh/sokol/issues/726).
The original case is no longer reproducible, so undoing the old workaround seems to
be a quick fix. Eventually I want to get rid of MTKView though, and go down to
CAMetalLayer.
- **08-Oct-2022** sokol_app.h Android backend: the ```sapp_touchpoint``` struct
now has a new item ```sapp_android_tooltype android_tooltype;```. This exposes the
result of the Android NDK function ```AMotionEvent_getToolType()```.
Many thanks to @Wertzui123 for the initial PR (https://github.com/floooh/sokol/pull/717).
- **25-Sep-2022**: sokol_app.h on Linux now optionally supports EGL instead of
GLX for the window system glue code and can create a GLES2 or GLES3 context
instead of a 'desktop GL' context.
To get EGL+GLES2/GLES3, just define SOKOL_GLES2 or SOKOL_GLES3 to compile the
implementation. To get EGL+GL, define SOKOL_GLCORE33 *and* SOKOL_FORCE_EGL.
By default, defining just SOKOL_GLCORE33 uses GLX for the window system glue
(just as before). Many thanks to GH user @billzez for the PR!
- **10-Sep-2022**: sokol_app.h and sokol_args.h has been fixed for Emscripten 3.21, those headers
used the Emscripten Javascript helper function ```ccall()``` which is now part of the
'legacy runtime' and causes linker errors. Instead of ```ccall()``` sokol_app.h and sokol_args.h
now drop down to a lower level set of Emscripten JS helper functions (which hopefully won't
go away anytime soon).
- **05-Aug-2022**: New officially supported and automatically updated language bindings for Odin:
https://github.com/floooh/sokol-odin (also see [gen_odin.py](https://github.com/floooh/sokol/blob/master/bindgen/gen_odin.py))
- **10-Jul-2022**: New features in sokol_app.h and sokol_imgui.h:
- In sokol_app.h it's now possible to set a mouse cursor type from a number of predefined
types via the new function ```sapp_set_mouse_cursor(sapp_mouse_cursor cursor)```. The
available cursor types are compatible with GLFW and Dear ImGui. Supported platforms
are: macOS, linux, win32, uwp and web.
- ```sapp_show_mouse(bool shown)``` now also works on the web platform.
- In sokol_app.h, the poorly defined 'user cursor' feature has been removed (```sapp_desc.user_cursor```
and ```SAPP_EVENTTYPE_UPDATE_CURSOR```). This was a hack to allow changing the mouse cursor and
only worked on Win32 and macOS (with different behaviour). Since setting the cursor type
is now 'properly supported, this hack was removed.
- sokol_imgui.h will now set the cursor type via ```sapp_set_mouse_cursor()```. This can be
disabled with the new ```simgui_desc_t``` item ```disable_set_mouse_cursor```.
- sokol_imgui.h now automatically enables resizing windows from edges (not just the bottom-right corner),
this behaviour can be disabled with the new ```simgui_desc_t``` item ```disable_windows_resize_from_edges```.
- sokol_imgui.h can now optionally write to the alpha channel (useful if you want to render the UI
into a separate render target, which is later composed onto the default framebuffer). The feature
is enabled with the new ```simgui_desc_t``` item ```write_alpha_channel```.
Many thanks to **@tomc1998** for the initial [Linux/X11 mouse cursor type PR](https://github.com/floooh/sokol/pull/678) and
**@luigi-rosso** for the [sokol_imgui.h alpha channel PR](https://github.com/floooh/sokol/pull/687)!
- **03-Jul-2022**: A new sokol_gfx.h function ```bool sg_query_buffer_will_overflow(sg_buffer buf, size_t size)```
which allows to check if a call to ```sg_append_buffer()``` would overflow the buffer. This
is an alternative to the ```sg_query_buffer_overflow()``` function which only reports
the overflow after the fact. Many thanks to @RandyGaul for the PR!
- **29-Jun-2022**: In sokol_app.h with the D3D11 backend, if SOKOL_DEBUG is
defined, and the D3D11 device creation fails, there's now a fallback code
path which tries to create the device again without the D3D11_CREATE_DEVICE_DEBUG
flag. Turns out the D3D11 debug support may suddenly stop working (just happened
to me, indicated by the Win10 "Graphics Tool" feature being silently uninstalled
and failing to install when asked to do so). This fix at least allows sokol_app.h
applications compiled in debug mode to run, even if the D3D11 debug layer doesn't
work.
- **29-May-2022**: The code generation scripts for the
[sokol-nim](https://github.com/floooh/sokol-nim) language bindings have been
revised and updated, many thanks to Gustav Olsson for the PR! (I'm planning to
spend a few more days integrating the bindings generation with Github Actions,
so that it's easier to publish new bindings after updates to the sokol headers).
- **26-May-2022**: The GL backend in sokol_app.h now allows to override the GL
context version via two new items in the ```sapp_desc``` struct:
```sapp_desc.gl_major_version``` and ```sapp_desc.gl_minor_version```. The
default GL context version remains at 3.2. Overriding the GL version might make
sense if you're not using sokol_app.h together with sokol_gfx.h, or otherwise
want to call GL functions directly. Note that this only works for the
'desktop GL' backends (Windows, Linux and macOS), but not for the GLES backends
(Android, iOS, web). Furthermore, on macOS only the GL versions 3.2 and 4.1
are available (plus the special config major=1 minor=0 creates an
NSOpenGLProfileVersionLegacy context). In general: use at your risk :) Many
thanks to Github user @pplux for the PR!
- **15-May-2022**: The way internal memory allocation can be overridden with
your own functions has been changed from global macros to callbacks
provided in the API setup call. For instance in sokol_gfx.h:
```c
void* my_malloc(size_t size, void* userdata) {
(void)userdata; // unused
return malloc(size);
}
void my_free(void* ptr, void* userdata) {
(void)userdata; // unused
free(ptr);
}
//...
sg_setup(&(sg_desc){
//...
.allocator = {
.alloc = my_malloc,
.free = my_free,
.user_data = ...,
}
});
```
sokol_gfx.h will now call ```my_malloc()``` and ```my_free()``` whenever it needs
to allocate or free memory (note however that allocations inside OS
functions or 3rd party libraries are not affected).
If no override functions are provided, the standard library functions ```malloc()``` and ```free()```
will be used, just as before.
This change breaks source compatibility in the following headers:
- **sokol_fontstash.h**: the function signature of ```sfons_create()``` has changed,
this now takes a pointer to a new ```sfons_desc_t``` struct instead of
individual parameters.
- **sokol_gfx_imgui.h** (NOT sokol_imgui.h!): likewise, the function signature of
```sg_imgui_init()``` has changed, this now takes an additional parameter
which is a pointer to a new ```sg_imgui_desc_t``` struct.
All affected headers also have a preprocessor check for the outdated
macros ```SOKOL_MALLOC```, ```SOKOL_CALLOC``` and ```SOKOL_FREE``` and throw
a compilation error if those macros are detected.
(if configuration through macros is still desired this could be added back in
the future, but I figured that the new way is more flexible in most situations).
The header sokol_memtrack.h and the sample [restart-sapp](https://floooh.github.io/sokol-html5/restart-sapp.html) have been updated accordingly.
Also search for ```MEMORY ALLOCATION OVERRIDE``` in the header documentation block
for more details.
- **14-May-2022**: added a helper function ```simgui_map_keycode()``` to
sokol_imgui.h to map sokol_app.h keycodes (```sapp_keycode```,
```SAPP_KEYCODE_*```) to Dear ImGui keycodes (```ImGuiKey```, ```ImGuiKey_*```).
If you're using Dear ImGui function to check for key input, you'll need to
update the code like this:
- Old:
```cpp
ImGui::IsKeyPressed(SAPP_KEYCODE_A);
```
- New:
```cpp
ImGui::IsKeyPressed(simgui_map_keycode(SAPP_KEYCODE_A));
```
This was basically 'fallout' from rewriting the input system in sokol_imgui.h
to the new evented IO system in Dear ImGui.
- **08-Feb-2022**: sokol_imgui.h has been updated for Dear ImGui 1.87:
- sokol_imgui.h's input code has been rewritten to use the new evented IO
system and extended virtual key codes in Dear ImGui
- on non-Emscripten platforms, mouse buttons are no longer "cancelled" when
the mouse leaves the window (since the native desktop platforms
automatically capture the mouse when mouse buttons are pressed, but mouse
capture is not supported in the sokol_app.h Emscripten backend)
- **28-Jan-2022**: some window size behaviour changes in sokol_app.h.
- Asking for a default-sized window (via sapp_desc.width/height = 0) now
behaves a bit differently on desktop platforms. Previously this set the
window size to 640x480, now a default window covers more screen area:
- on Windows CW_USEDEFAULT will be used for the size
- on macOS and Linux, the window size will be 4/5 of the
display size
- no behaviour changes on other platforms
- On Windows and Linux, the window is now centered (in a later update,
more control over the initial window position, and new functions for
positioning and sizing might be provided)
- On Windows, when toggling between windowed and fullscreen, the
window position and size will now be restored (on other platforms
this already happened automatically through the window system)
- On all desktop platforms if an application starts in fullscreen and
then is toggled back to windowed, the window will now be of the
expected size (provided in sapp_desc.width/height)
- **20-Jan-2022**:
- sokol_audio.h: A compatibility fix in the sokol_audio.h WASAPI backend (Windows): On
some configs the IAudioClient::Initialize() call could fail because
of a mismatch between the requested number of channels and speaker config.
See [#614](https://github.com/floooh/sokol/issues/614) for details.
- sokol_app.h D3D11/DXGI: Fix an (uncritical) COM interface leak warning for IDXGIAdapter and
IDXGIFactory at shutdown, introduced with the recent disabling of Alt-Enter.
- **18-Jan-2022**:
- sokol_app.h now has per-monitor DPI support on Windows and macOS: when
the application window is moved to a monitor with different DPI, the values
returned by sapp_dpi_scale(), sapp_width() and sapp_height() will update
accordingly (only if the application requested high-dpi rendering with
```sapp_desc.high_dpi=true```, otherwise the dpi scale value remains
fixed at 1.0f). The application will receive an SAPP_EVENTTYPE_RESIZED event
if the default framebuffer size has changed because of a DPI change.
On Windows this feature requires Win10 version 1703 or later (aka the
'Creators Update'), older Windows version simply behave as before.
Many thank to @tjachmann for the initial PR with the Windows implementation!
- sokol_app.h: DPI scale computation on macOS is now more robust using the
NSScreen.backingScaleFactor value
- sokol_app.h: the new frame timing code in sokol_app.h now detects if the display
refresh rate changes and adjusts itself accordingly (for instance if the
window is moved between displays with different refresh rate)
- sokol_app.h D3D11/DXGI: during window movement and resize, the frame is now
presented with DXGI_PRESENT_DO_NOT_WAIT, this fixes some window system
stuttering issues on Win10 configs with recent NVIDIA drivers.
- sokol_app.h D3D11/DXGI: the application will no longer appear to freeze for
0.5 seconds when the title bar is grabbed with the mouse for movement, but
then not moving the mouse.
- sokol_app.h D3D11/DXGI: DXGI's automatic windowed/fullscreen switching via
Alt-Enter has been disabled, because this switched to 'real' fullscreen mode,
while sokol_app.h's fullscreen mode uses a borderless window. Use the
programmatic fullscreen/window switching via ```sapp_toggle_fullscreen()```
instead.
- **BREAKING CHANGE** in sokol_imgui.h: because the applications' DPI scale
can now change at any time, the DPI scale value is now communicated to
sokol_imgui.h in the ```simgui_new_frame()``` function. This has been
changed to accept a pointer to a new ```simgui_frame_desc_t``` struct.
With C99, change the simgui_new_frame() call as follows (if also using
sokol_app.h):
```c
simgui_new_frame(&(simgui_frame_desc_t){
.width = sapp_width(),
.height = sapp_height(),
.delta_time = sapp_frame_duration(),
.dpi_scale = sapp_dpi_scale()
});
```
On C++ this works:
```c++
simgui_new_frame({ sapp_width(), sapp_height(), sapp_frame_duration(), sapp_dpi_scale() });
```
...or in C++20:
```c++
simgui_new_frame({
.width = sapp_width(),
.height = sapp_height(),
.delta_time = sapp_frame_duration(),
.dpi_scale = sapp_dpi_scale()
});
```
- **KNOWN ISSUE**: the recent change in sokol-audio's WASAPI backend to directly consume
float samples doesn't appear to work on some configs (see [#614](https://github.com/floooh/sokol/issues/614)),
investigation is underway
- **15-Jan-2022**:
- A bugfix in the GL backend for uniform arrays using the 'native' uniform block layout.
The bug was a regression in the recent 'uniform data handling' update. See
[PR #611](https://github.com/floooh/sokol/pull/611) for details, and this [new sample/test](https://github.com/floooh/sokol-samples/blob/master/glfw/uniformarrays-glfw.c).
Many thanks to @nmr8acme for the PR!
- **08-Jan-2022**: some enhancements and cleanup to uniform data handling in sokol_gfx.h
and the sokol-shdc shader compiler:
- *IMPORTANT*: when updating sokol_gfx.h (and you're using the sokol-shdc shader compiler),
don't forget to update the sokol-shdc binaries too!
- The GLSL uniform types int, ivec2, ivec3 and
ivec4 can now be used in shader code, those are exposed to the GL
backends with the new ```sg_uniform_type``` items
```SG_UNIFORM_TYPE_INT[2,3,4]```.
- A new enum ```sg_uniform_layout```, currently with the values SG_UNIFORMLAYOUT_NATIVE
and SG_UNIFORMLAYOUT_STD140. The enum is used in ```sg_shader_uniform_block_desc```
as a 'packing rule hint', so that the GL backend can properly locate the offset
of uniform block members. The default (SG_UNIFORMLAYOUT_NATIVE) keeps the same
behaviour, so existing code shouldn't need to be changed. With the packing
rule SG_UNIFORMLAYOUT_STD140 the uniform block interior is expected to be
layed out according to the OpenGL std140 packing rule.
- Note that the SG_UNIFORMLAYOUT_STD140 only allows a subset of the actual std140
packing rule: arrays are only allowed for the types vec4, int4 and mat4.
This is because the uniform data must still be compatible with
```glUniform()``` calls in the GL backends (which have different
'interior alignment' for arrays).
- The sokol-shdc compiler supports the new uniform types and will annotate the
code-generated sg_shader_desc structs with SG_UNIFORMLAYOUT_STD140,
and there are new errors to make sure that uniform blocks are compatible
with all sokol_gfx.h backends.
- Likewise, sokol_gfx.h has tighter validation for the ```sg_shader_uniform_block```
desc struct, but only when the GL backend is used (in general, the interior
layout of uniform blocks is only relevant for GL backends, on all other backends
sokol_gfx.h just passes the uniform data as an opaque block to the shader)
For more details see:
- [new sections in the sokol_gfx.h documentation](https://github.com/floooh/sokol/blob/ba64add0b67cac16fc86fb6b64d1da5f67e80c0f/sokol_gfx.h#L343-L450)
- [documentation of ```sg_uniform_layout```](https://github.com/floooh/sokol/blob/ba64add0b67cac16fc86fb6b64d1da5f67e80c0f/sokol_gfx.h#L1322-L1355)
- [enhanced sokol-shdc documentation](https://github.com/floooh/sokol-tools/blob/master/docs/sokol-shdc.md#glsl-uniform-blocks-and-c-structs)
- [a new sample 'uniformtypes-sapp'](https://floooh.github.io/sokol-html5/uniformtypes-sapp.html)
PS: and an unrelated change: the frame latency on Win32+D3D11 has been slightly improved
via IDXGIDevice1::SetMaximumFrameLatency()
- **27-Dec-2021**: sokol_app.h frame timing improvements:
- A new function ```double sapp_frame_duration(void)``` which returns the frame
duration in seconds, averaged over the last 256 frames to smooth out
jittering spikes. If available, this uses platform/backend specific
functions of the swapchain API:
- On Windows: DXGI's GetFrameStatistics().SyncQPCTime.
- On Emscripten: the timestamp provided by the RAF callback, this will
still be clamped and jittered on some browsers, but averaged over
a number of frames yields a pretty accurate approximation
of the actual frame duration.
- On Metal, ```MTLDrawable addPresentedHandler + presentedTime```
doesn't appear to function correctly on macOS Monterey and/or M1 Macs, so
instead mach_absolute_time() is called at the start of the MTKView
frame callback.
- In all other situations, the same timing method is used as
in sokol_time.h.
- On macOS and iOS, sokol_app.h now queries the maximum display refresh rate
of the main display and uses this as base to compute the preferred frame
rate (by multiplying with ```sapp_desc.swap_interval```), previously the
preferred frame rate was hardwired to ```60 * swap_interval```. This means
that native macOS and iOS applications may now run at 120Hz instead of
60Hz depending on the device (I realize that this isn't ideal, there
will probably be a different way to hint the preferred interval at
which the frame callback is called, which would also support disabling
vsync and probably also adaptive vsync).
- **19-Dec-2021**: some sokol_audio.h changes:
- on Windows, sokol_audio.h no longer converts audio samples
from float to int16_t, but instead configures WASAPI to directly accept
float samples. Many thanks to github user iOrange for the PR!
- sokol_audio.h has a new public function ```saudio_suspended()``` which
returns true if the audio device/context is currently in suspended mode.
On all backends except WebAudio this always returns false. This allows
to show a visual hint to the user that audio is muted until the first
input event is received.
- **18-Dec-2021**: the sokol_gfx.h ```sg_draw()``` function now uses the currently applied
pipeline object to decide if the GL or D3D11 backend's instanced drawing function
should be called instead of the ```num_instances``` argument. This fixes a
bug on some WebGL configs when instanced rendering is configured
but ```sg_draw()``` is called with an instance count of 1.
- **18-Nov-2021**: sokol_gl.h has a new function to control the point size for
point list rendering: ```void sgl_point_size(float size)```. Note that on D3D11
the point size is currently ignored (since D3D11 doesn't support a point size at
all, the feature will need to be emulated in sokol_gl.h when the D3D11 backend is active).
Also note that points cannot currently be textured, only colored.
- **08-Oct-2021**: texture compression support in sokol_gfx.h has been revisited:
- tighter validation checks on texture creation:
- content data validation now also happens in ```sg_make_image()``` (previously only in ```sg_update_image()```)
@ -67,7 +753,7 @@
ago (around iOS 12.x) MTKView started to ignore the contentScaleFactor
property, which lead to sokol_app.h always setting up a HighDPI
framebuffer even when sapp_desc.high_dpi wasn't set. The fix is to set
the MTKView's drawableSize explicitely now.
the MTKView's drawableSize explicitly now.
- The iOS GL backend didn't support MSAA multisampling so far, this has
been fixed now, but only one MSAA mode (4x) is available, which will be
selected when sapp_desc.sample_count is greater than 1.
@ -121,7 +807,7 @@ uses the **AVAudioSession** class to activate and deactivate audio output as nee
This fixes sokol_audio.h for iPhones (so far, sokol_audio.h accidentally only worked
for iPads). Please see [this issue](https://github.com/floooh/sokol/issues/431) for details.
A somewhat unfortunate side effect of this fix is that sokol_audio.h must now be compiled
as Objective-C when targetting iOS, also note that a new framework must be linked: ```AVFoundation```.
as Objective-C when targeting iOS, also note that a new framework must be linked: ```AVFoundation```.
Many thanks to @oviano for providing the PR!
- **14-Feb-2021**: The Dear ImGui rendering backend in [sokol_imgui.h](https://github.com/floooh/sokol/blob/master/util/sokol_imgui.h) has been rewritten to only do a single

View File

@ -4,3 +4,5 @@
__pycache__/
sokol-nim/
sokol-zig/
sokol-odin/
sokol-rust/

View File

@ -1,8 +1,10 @@
import os, gen_nim, gen_zig
import os, gen_nim, gen_zig, gen_odin, gen_rust
tasks = [
[ '../sokol_log.h', 'slog_', [] ],
[ '../sokol_gfx.h', 'sg_', [] ],
[ '../sokol_app.h', 'sapp_', [] ],
[ '../sokol_glue.h', 'sapp_sg', ['sg_'] ],
[ '../sokol_time.h', 'stm_', [] ],
[ '../sokol_audio.h', 'saudio_', [] ],
[ '../util/sokol_gl.h', 'sgl_', ['sg_'] ],
@ -10,18 +12,26 @@ tasks = [
[ '../util/sokol_shape.h', 'sshape_', ['sg_'] ],
]
# Odin
gen_odin.prepare()
for task in tasks:
[c_header_path, main_prefix, dep_prefixes] = task
gen_odin.gen(c_header_path, main_prefix, dep_prefixes)
# Nim
gen_nim.prepare()
for task in tasks:
c_header_path = task[0]
main_prefix = task[1]
dep_prefixes = task[2]
[c_header_path, main_prefix, dep_prefixes] = task
gen_nim.gen(c_header_path, main_prefix, dep_prefixes)
# Zig
gen_zig.prepare()
for task in tasks:
c_header_path = task[0]
main_prefix = task[1]
dep_prefixes = task[2]
[c_header_path, main_prefix, dep_prefixes] = task
gen_zig.gen(c_header_path, main_prefix, dep_prefixes)
# Rust
gen_rust.prepare()
for task in tasks:
[c_header_path, main_prefix, dep_prefixes] = task
gen_rust.gen(c_header_path, main_prefix, dep_prefixes)

View File

@ -59,11 +59,11 @@ def parse_enum(decl):
if 'inner' in item_decl:
const_expr = item_decl['inner'][0]
if const_expr['kind'] != 'ConstantExpr':
sys.exit(f"ERROR: Enum values must be a ConstantExpr ({decl['name']})")
if const_expr['valueCategory'] != 'rvalue':
sys.exit(f"ERROR: Enum value ConstantExpr must be 'rvalue' ({decl['name']})")
sys.exit(f"ERROR: Enum values must be a ConstantExpr ({item_decl['name']}), is '{const_expr['kind']}'")
if const_expr['valueCategory'] != 'rvalue' and const_expr['valueCategory'] != 'prvalue':
sys.exit(f"ERROR: Enum value ConstantExpr must be 'rvalue' or 'prvalue' ({item_decl['name']}), is '{const_expr['valueCategory']}'")
if not ((len(const_expr['inner']) == 1) and (const_expr['inner'][0]['kind'] == 'IntegerLiteral')):
sys.exit(f"ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({decl['name']})")
sys.exit(f"ERROR: Enum value ConstantExpr must have exactly one IntegerLiteral ({item_decl['name']})")
item['value'] = const_expr['inner'][0]['value']
if needs_value and 'value' not in item:
sys.exit(f"ERROR: anonymous enum items require an explicit value")
@ -79,7 +79,7 @@ def parse_func(decl):
if 'inner' in decl:
for param in decl['inner']:
if param['kind'] != 'ParmVarDecl':
print(f"warning: ignoring func {decl['name']} (unsupported parameter type)")
print(f" >> warning: ignoring func {decl['name']} (unsupported parameter type)")
return None
outp_param = {}
outp_param['name'] = param['name']
@ -119,4 +119,6 @@ def gen(header_path, source_path, module, main_prefix, dep_prefixes):
outp_decl['is_dep'] = is_dep
outp_decl['dep_prefix'] = dep_prefix(decl, dep_prefixes)
outp['decls'].append(outp_decl)
with open(f'{module}.json', 'w') as f:
f.write(json.dumps(outp, indent=2));
return outp

View File

@ -1,16 +1,19 @@
#-------------------------------------------------------------------------------
# Read output of gen_json.py and generate Zig language bindings.
# Generate Nim bindings
#
# Nim coding style:
# - types and constants are PascalCase
# - functions, parameters, and fields are camelCase
# - type identifiers are PascalCase, everything else is camelCase
# - reference: https://nim-lang.org/docs/nep1.html
#-------------------------------------------------------------------------------
import gen_ir
import json, re, os, shutil
import gen_util as util
import os, shutil, sys
module_names = {
'slog_': 'log',
'sg_': 'gfx',
'sapp_': 'app',
'sapp_sg': 'glue',
'stm_': 'time',
'saudio_': 'audio',
'sgl_': 'gl',
@ -19,8 +22,10 @@ module_names = {
}
c_source_paths = {
'slog_': 'sokol-nim/src/sokol/c/sokol_log.c',
'sg_': 'sokol-nim/src/sokol/c/sokol_gfx.c',
'sapp_': 'sokol-nim/src/sokol/c/sokol_app.c',
'sapp_sg': 'sokol-nim/src/sokol/c/sokol_glue.c',
'stm_': 'sokol-nim/src/sokol/c/sokol_time.c',
'saudio_': 'sokol-nim/src/sokol/c/sokol_audio.c',
'sgl_': 'sokol-nim/src/sokol/c/sokol_gl.c',
@ -28,20 +33,57 @@ c_source_paths = {
'sshape_': 'sokol-nim/src/sokol/c/sokol_shape.c',
}
func_name_ignores = [
c_callbacks = [
'slog_func',
]
ignores = [
'sdtx_printf',
'sdtx_vprintf',
]
func_name_overrides = {
'sgl_error': 'sgl_get_error', # 'error' is reserved in Zig
'sgl_deg': 'sgl_as_degrees',
'sgl_rad': 'sgl_as_radians',
}
struct_field_type_overrides = {
overrides = {
'sgl_error': 'sgl_get_error',
'sgl_deg': 'sgl_as_degrees',
'sgl_rad': 'sgl_as_radians',
'sg_context_desc.color_format': 'int',
'sg_context_desc.depth_format': 'int',
'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR',
'SG_BUFFERTYPE_VERTEXBUFFER': 'SG_BUFFERTYPE_VERTEX_BUFFER',
'SG_BUFFERTYPE_INDEXBUFFER': 'SG_BUFFERTYPE_INDEX_BUFFER',
'SG_ACTION_DONTCARE': 'SG_ACTION_DONT_CARE',
'ptr': 'addr', # range ptr
'func': 'fn',
'slog_func': 'fn',
}
enumPrefixOverrides = {
# sokol_gfx.h
'PIXELFORMAT': 'pixelFormat',
'RESOURCESTATE': 'resourceState',
'BUFFERTYPE': 'bufferType',
'INDEXTYPE': 'indexType',
'IMAGETYPE': 'imageType',
'SAMPLERTYPE': 'samplerType',
'CUBEFACE': 'cubeFace',
'SHADERSTAGE': 'shaderStage',
'PRIMITIVETYPE': 'primitiveType',
'BORDERCOLOR': 'borderColor',
'VERTEXFORMAT': 'vertexFormat',
'VERTEXSTEP': 'vertexStep',
'UNIFORMTYPE': 'uniformType',
'UNIFORMLAYOUT': 'uniformLayout',
'CULLMODE': 'cullMode',
'FACEWINDING': 'faceWinding',
'COMPAREFUNC': 'compareFunc',
'STENCILOP': 'stencilOp',
'BLENDFACTOR': 'blendFactor',
'BLENDOP': 'blendOp',
'COLORMASK': 'colorMask',
# sokol_app.h
'EVENTTYPE': 'eventType',
'KEYCODE': 'keyCode',
'MOUSEBUTTON': 'mouseButton',
}
prim_types = {
@ -60,7 +102,7 @@ prim_types = {
'double': 'float64',
'uintptr_t': 'uint',
'intptr_t': 'int',
'size_t': 'int',
'size_t': 'int', # not a bug, Nim's sizeof() returns int
}
prim_defaults = {
@ -74,31 +116,62 @@ prim_defaults = {
'uint32_t': '0',
'int64_t': '0',
'uint64_t': '0',
'float': '0.0',
'float': '0.0f',
'double': '0.0',
'uintptr_t': '0',
'intptr_t': '0',
'size_t': '0'
}
common_prim_types = """
array
untyped typed void
bool byte char
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64
float float32 float64
string
cchar cint csize_t
cfloat cdouble
cstring
pointer
""".split()
keywords = """
addr and as asm
bind block break
case cast concept const continue converter
defer discard distinct div do
elif else end enum except export
finally for from func
if import in include interface is isnot iterator
let
macro method mixin mod
nil not notin
object of or out
proc ptr
raise ref return
shl shr static
template try tuple type
using
var
when while
xor
yield
""".split() + common_prim_types
struct_types = []
enum_types = []
enum_items = {}
out_lines = ''
def reset_globals():
global struct_types
global enum_types
global enum_items
global out_lines
struct_types = []
enum_types = []
enum_items = {}
out_lines = ''
re_1d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]$")
re_2d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]\[\d*\]$")
def l(s):
global out_lines
out_lines += s + '\n'
@ -107,90 +180,66 @@ def as_nim_prim_type(s):
return prim_types[s]
# prefix_bla_blub(_t) => (dep.)BlaBlub
def as_nim_struct_type(s, prefix):
def as_nim_type_name(s, prefix):
parts = s.lower().split('_')
outp = '' if s.startswith(prefix) else f'{parts[0]}.'
dep = parts[0] + '_'
outp = ''
if not s.startswith(prefix) and dep in module_names:
outp = module_names[dep] + '.'
for part in parts[1:]:
# ignore '_t' type postfix
if (part != 't'):
outp += part.capitalize()
return outp
# prefix_bla_blub(_t) => (dep.)BlaBlub
def as_nim_enum_type(s, prefix):
parts = s.lower().split('_')
outp = '' if s.startswith(prefix) else f'{parts[0]}.'
for part in parts[1:]:
if (part != 't'):
outp += part.capitalize()
return outp
# prefix_bla_blub(_t) => (dep.)BlaBlub
def as_nim_const_type(s, prefix):
parts = s.lower().split('_')
outp = '' if s.startswith(prefix) else f'{parts[0]}.'
for part in parts[1:]:
if (part != 't'):
outp += part.capitalize()
return outp
def check_struct_field_type_override(struct_name, field_name, orig_type):
s = f"{struct_name}.{field_name}"
if s in struct_field_type_overrides:
return struct_field_type_overrides[s]
def check_override(name, default=None):
if name in overrides:
return overrides[name]
elif default is None:
return name
else:
return orig_type
return default
def check_func_name_ignore(func_name):
return func_name in func_name_ignores
def check_ignore(name):
return name in ignores
def check_func_name_override(func_name):
if func_name in func_name_overrides:
return func_name_overrides[func_name]
def is_power_of_two(val):
return val == 0 or val & (val - 1) == 0
def wrap_keywords(s):
if s in keywords:
return f'`{s}`'
else:
return func_name
def trim_prefix(s, prefix):
outp = s;
if outp.lower().startswith(prefix.lower()):
outp = outp[len(prefix):]
return outp
# PREFIX_BLA_BLUB to bla_blub
def as_snake_case(s, prefix = ""):
return trim_prefix(s, prefix).lower()
return s
# prefix_bla_blub => blaBlub
def as_camel_case(s, prefix = ""):
parts = trim_prefix(s, prefix).lower().split('_')
def as_camel_case(s, prefix, wrap=True):
outp = s.lower()
if outp.startswith(prefix):
outp = outp[len(prefix):]
parts = outp.lstrip('_').split('_')
outp = parts[0]
for part in parts[1:]:
outp += part.capitalize()
if wrap:
outp = wrap_keywords(outp)
return outp
# prefix_bla_blub => BlaBlub
def as_pascal_case(s, prefix):
parts = trim_prefix(s, prefix).lower().split('_')
outp = ""
for part in parts:
# PREFIX_ENUM_BLA_BLO => blaBlo
def as_enum_item_name(s, wrap=True):
outp = s.lstrip('_')
parts = outp.split('_')[1:]
if parts[0] in enumPrefixOverrides:
parts[0] = enumPrefixOverrides[parts[0]]
else:
parts[0] = parts[0].lower()
outp = parts[0]
for part in parts[1:]:
outp += part.capitalize()
if wrap:
outp = wrap_keywords(outp)
return outp
# PREFIX_ENUM_BLA => Bla, _PREFIX_ENUM_BLA => Bla
def as_enum_item_name(s):
outp = s
if outp.startswith('_'):
outp = outp[1:]
parts = outp.lower().split('_')[2:]
outp = ""
for part in parts:
outp += part.capitalize()
if outp[0].isdigit():
outp = 'N' + outp
return outp
def enum_default_item(enum_name):
return enum_items[enum_name][0]
def is_prim_type(s):
return s in prim_types
@ -200,15 +249,6 @@ def is_struct_type(s):
def is_enum_type(s):
return s in enum_types
def is_string_ptr(s):
return s == "const char *"
def is_const_void_ptr(s):
return s == "const void *"
def is_void_ptr(s):
return s == "void *"
def is_const_prim_ptr(s):
for prim_type in prim_types:
if s == f"const {prim_type} *":
@ -227,266 +267,208 @@ def is_const_struct_ptr(s):
return True
return False
def is_func_ptr(s):
return '(*)' in s
def is_1d_array_type(s):
return re_1d_array.match(s)
def is_2d_array_type(s):
return re_2d_array.match(s)
def type_default_value(s):
return prim_defaults[s]
def extract_array_type(s):
return s[:s.index('[')].strip()
def extract_array_nums(s):
return s[s.index('['):].replace('[', ' ').replace(']', ' ').split()
def extract_ptr_type(s):
tokens = s.split()
if tokens[0] == 'const':
return tokens[1]
else:
return tokens[0]
def as_extern_c_arg_type(arg_type, prefix):
if arg_type == "void":
return "void"
elif is_prim_type(arg_type):
return as_nim_prim_type(arg_type)
elif is_struct_type(arg_type):
return as_nim_struct_type(arg_type, prefix)
elif is_enum_type(arg_type):
return as_nim_enum_type(arg_type, prefix)
elif is_void_ptr(arg_type):
return "pointer"
elif is_const_void_ptr(arg_type):
return "pointer"
elif is_string_ptr(arg_type):
return "cstring"
elif is_const_struct_ptr(arg_type):
return f"ptr {as_nim_struct_type(extract_ptr_type(arg_type), prefix)}"
elif is_prim_ptr(arg_type):
return f"[*c] {as_nim_prim_type(extract_ptr_type(arg_type))}"
elif is_const_prim_ptr(arg_type):
return f"ptr {as_nim_prim_type(extract_ptr_type(arg_type))}"
else:
return '??? (as_extern_c_arg_type)'
def as_nim_arg_type(arg_prefix, arg_type, prefix):
# NOTE: if arg_prefix is None, the result is used as return value
pre = "" if arg_prefix is None else arg_prefix
if arg_type == "void":
if arg_prefix is None:
return "void"
else:
return ""
elif is_prim_type(arg_type):
return pre + as_nim_prim_type(arg_type)
elif is_struct_type(arg_type):
return pre + as_nim_struct_type(arg_type, prefix)
elif is_enum_type(arg_type):
return pre + as_nim_enum_type(arg_type, prefix)
elif is_void_ptr(arg_type):
return pre + "pointer"
elif is_const_void_ptr(arg_type):
return pre + "pointer"
elif is_string_ptr(arg_type):
return pre + "cstring"
elif is_const_struct_ptr(arg_type):
return pre + f"ptr {as_nim_struct_type(extract_ptr_type(arg_type), prefix)}"
elif is_prim_ptr(arg_type):
return pre + f"ptr {as_nim_prim_type(extract_ptr_type(arg_type))}"
elif is_const_prim_ptr(arg_type):
return pre + f"ptr {as_nim_prim_type(extract_ptr_type(arg_type))}"
else:
return arg_prefix + "??? (as_nim_arg_type)"
# get C-style arguments of a function pointer as string
def funcptr_args_c(field_type, prefix):
def funcptr_args(field_type, prefix):
tokens = field_type[field_type.index('(*)')+4:-1].split(',')
s = ""
n = 0
for token in tokens:
n += 1
arg_type = token.strip()
arg_ctype = token.strip()
if s != "":
s += ", "
c_arg = f"a{n}:" + as_extern_c_arg_type(arg_type, prefix)
if (c_arg == "void"):
return ""
else:
s += c_arg
arg_nimtype = as_nim_type(arg_ctype, prefix)
if arg_nimtype == "":
return "" # fun(void)
s += f"a{n}:{arg_nimtype}"
if s == "a1:void":
s = ""
return s
# get C-style result of a function pointer as string
def funcptr_res_c(field_type):
res_type = field_type[:field_type.index('(*)')].strip()
if res_type == 'void':
return ''
elif is_const_void_ptr(res_type):
return ':pointer'
else:
return '???'
def funcptr_result(field_type, prefix):
ctype = field_type[:field_type.index('(*)')].strip()
return as_nim_type(ctype, prefix)
def funcdecl_args_c(decl, prefix):
s = ""
for param_decl in decl['params']:
if s != "":
s += ", "
arg_type = param_decl['type']
s += as_extern_c_arg_type(arg_type, prefix)
return s
def funcdecl_args_nim(decl, prefix):
s = ""
for param_decl in decl['params']:
if s != "":
s += ", "
arg_name = param_decl['name']
arg_type = param_decl['type']
s += f"{as_nim_arg_type(f'{arg_name}:', arg_type, prefix)}"
return s
def funcdecl_res_c(decl, prefix):
decl_type = decl['type']
res_type = decl_type[:decl_type.index('(')].strip()
return as_extern_c_arg_type(res_type, prefix)
def funcdecl_res_nim(decl, prefix):
decl_type = decl['type']
res_type = decl_type[:decl_type.index('(')].strip()
nim_res_type = as_nim_arg_type(None, res_type, prefix)
if nim_res_type == "":
nim_res_type = "void"
return nim_res_type
def gen_struct(decl, prefix, use_raw_name=False):
struct_name = decl['name']
nim_type = struct_name if use_raw_name else as_nim_struct_type(struct_name, prefix)
l(f"type {nim_type}* = object")
isPublic = True
for field in decl['fields']:
field_name = field['name']
if field_name == "__pad":
# FIXME: these should be guarded by SOKOL_ZIG_BINDINGS, but aren't?
continue
isPublic = not field_name.startswith("_")
field_name = as_camel_case(field_name, "_")
if field_name == "ptr":
field_name = "source"
if field_name == "ref":
field_name = "`ref`"
if field_name == "type":
field_name = "`type`"
if isPublic:
field_name += "*"
field_type = field['type']
field_type = check_struct_field_type_override(struct_name, field_name, field_type)
if is_prim_type(field_type):
l(f" {field_name}:{as_nim_prim_type(field_type)}")
elif is_struct_type(field_type):
l(f" {field_name}:{as_nim_struct_type(field_type, prefix)}")
elif is_enum_type(field_type):
l(f" {field_name}:{as_nim_enum_type(field_type, prefix)}")
elif is_string_ptr(field_type):
l(f" {field_name}:cstring")
elif is_const_void_ptr(field_type):
l(f" {field_name}:pointer")
elif is_void_ptr(field_type):
l(f" {field_name}:pointer")
elif is_const_prim_ptr(field_type):
l(f" {field_name}:ptr {as_nim_prim_type(extract_ptr_type(field_type))}")
elif is_func_ptr(field_type):
l(f" {field_name}:proc({funcptr_args_c(field_type, prefix)}){funcptr_res_c(field_type)} {{.cdecl.}}")
elif is_1d_array_type(field_type):
array_type = extract_array_type(field_type)
array_nums = extract_array_nums(field_type)
if is_prim_type(array_type) or is_struct_type(array_type):
if is_prim_type(array_type):
nim_type = as_nim_prim_type(array_type)
elif is_struct_type(array_type):
nim_type = as_nim_struct_type(array_type, prefix)
elif is_enum_type(array_type):
nim_type = as_nim_enum_type(array_type, prefix)
else:
nim_type = '??? (array type)'
t0 = f"array[{array_nums[0]}, {nim_type}]"
t0_slice = f"[]const {nim_type}"
t1 = f"[_]{nim_type}"
l(f" {field_name}:{t0}")
elif is_const_void_ptr(array_type):
l(f" {field_name}:array[{array_nums[0]}, pointer]")
else:
l(f"// FIXME: ??? array {field_name}:{field_type} => {array_type} [{array_nums[0]}]")
elif is_2d_array_type(field_type):
array_type = extract_array_type(field_type)
array_nums = extract_array_nums(field_type)
if is_prim_type(array_type):
nim_type = as_nim_prim_type(array_type)
def_val = type_default_value(array_type)
elif is_struct_type(array_type):
nim_type = as_nim_struct_type(array_type, prefix)
def_val = ".{ }"
else:
nim_type = "???"
def_val = "???"
t0 = f"array[{array_nums[0]}, array[{array_nums[1]}, {nim_type}]]"
l(f" {field_name}:{t0}")
def as_nim_type(ctype, prefix, struct_ptr_as_value=False):
if ctype == "void":
return ""
elif is_prim_type(ctype):
return as_nim_prim_type(ctype)
elif is_struct_type(ctype):
return as_nim_type_name(ctype, prefix)
elif is_enum_type(ctype):
return as_nim_type_name(ctype, prefix)
elif util.is_string_ptr(ctype):
return "cstring"
elif util.is_void_ptr(ctype) or util.is_const_void_ptr(ctype):
return "pointer"
elif is_const_struct_ptr(ctype):
nim_type = as_nim_type(util.extract_ptr_type(ctype), prefix)
if struct_ptr_as_value:
return f"{nim_type}"
else:
l(f"// FIXME: {field_name}:{field_type};")
return f"ptr {nim_type}"
elif is_prim_ptr(ctype) or is_const_prim_ptr(ctype):
return f"ptr {as_nim_type(util.extract_ptr_type(ctype), prefix)}"
elif util.is_func_ptr(ctype):
args = funcptr_args(ctype, prefix)
res = funcptr_result(ctype, prefix)
if res != "":
res = ":" + res
return f"proc({args}){res} {{.cdecl.}}"
elif util.is_1d_array_type(ctype):
array_ctype = util.extract_array_type(ctype)
array_sizes = util.extract_array_sizes(ctype)
return f'array[{array_sizes[0]}, {as_nim_type(array_ctype, prefix)}]'
elif util.is_2d_array_type(ctype):
array_ctype = util.extract_array_type(ctype)
array_sizes = util.extract_array_sizes(ctype)
return f'array[{array_sizes[0]}, array[{array_sizes[1]}, {as_nim_type(array_ctype, prefix)}]]'
else:
sys.exit(f"ERROR as_nim_type: {ctype}")
def as_nim_struct_name(struct_decl, prefix):
struct_name = check_override(struct_decl['name'])
nim_type = f'{as_nim_type_name(struct_name, prefix)}'
return nim_type
def as_nim_field_name(field_decl, prefix, check_private=True):
field_name = as_camel_case(check_override(field_decl['name']), prefix)
if check_private:
is_private = field_decl['name'].startswith('_')
if not is_private:
field_name += "*"
return field_name
def as_nim_field_type(struct_decl, field_decl, prefix):
return as_nim_type(check_override(f"{struct_decl['name']}.{field_decl['name']}", default=field_decl['type']), prefix)
def gen_struct(decl, prefix):
l(f"type {as_nim_struct_name(decl, prefix)}* = object")
for field in decl['fields']:
l(f" {as_nim_field_name(field, prefix)}:{as_nim_field_type(decl, field, prefix)}")
l("")
def gen_consts(decl, prefix):
l("const")
for item in decl['items']:
l(f" {trim_prefix(item['name'], prefix)}* = {item['value']}")
item_name = check_override(item['name'])
l(f" {as_camel_case(item_name, prefix)}* = {item['value']}")
l("")
def gen_enum(decl, prefix):
item_names_by_value = {}
value = -1
hasForceU32 = False
hasExplicitValues = False
has_explicit_values = False
for item in decl['items']:
itemName = item['name']
if itemName.endswith("_FORCE_U32"):
hasForceU32 = True
elif itemName.endswith("_NUM"):
item_name = check_override(item['name'])
if item_name.endswith("_NUM") or item_name.endswith("_FORCE_U32"):
continue
else:
if 'value' in item:
hasExplicitValues = True
has_explicit_values = True
value = int(item['value'])
else:
value += 1
item_names_by_value[value] = as_enum_item_name(item['name']);
if hasForceU32:
l(f"type {as_nim_enum_type(decl['name'], prefix)}* {{.pure, size:4.}} = enum")
else:
l(f"type {as_nim_enum_type(decl['name'], prefix)}* {{.pure.}} = enum")
if hasExplicitValues:
item_names_by_value[value] = as_enum_item_name(item_name)
enum_name_nim = as_nim_type_name(decl['name'], prefix)
l('type')
l(f" {enum_name_nim}* {{.size:sizeof(int32).}} = enum")
if has_explicit_values:
# Nim requires explicit enum values to be declared in ascending order
for value in sorted(item_names_by_value):
name = item_names_by_value[value]
l(f" {name} = {value},")
l(f" {name} = {value},")
else:
for name in item_names_by_value.values():
l(f" {name},")
l(f" {name},")
l("")
# returns C prototype compatible function args (with pointers)
def funcdecl_args_c(decl, prefix):
s = ""
func_name = decl['name']
for param_decl in decl['params']:
if s != "":
s += ", "
arg_name = param_decl['name']
arg_type = check_override(f'{func_name}.{arg_name}', default=param_decl['type'])
s += f"{as_camel_case(arg_name, prefix)}:{as_nim_type(arg_type, prefix)}"
return s
# returns Nim function args (pass structs by value)
def funcdecl_args_nim(decl, prefix):
s = ""
func_name = decl['name']
for param_decl in decl['params']:
if s != "":
s += ", "
arg_name = param_decl['name']
arg_type = check_override(f'{func_name}.{arg_name}', default=param_decl['type'])
s += f"{as_camel_case(arg_name, prefix)}:{as_nim_type(arg_type, prefix, struct_ptr_as_value=True)}"
return s
def funcdecl_result(decl, prefix):
func_name = decl['name']
decl_type = decl['type']
result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip())
nim_res_type = as_nim_type(result_type, prefix)
if nim_res_type == "":
nim_res_type = "void"
return nim_res_type
def gen_func_nim(decl, prefix):
c_func_name = decl['name']
nim_func_name = as_camel_case(decl['name'], prefix)
nim_res_type = funcdecl_res_nim(decl, prefix)
l(f"proc {nim_func_name}*({funcdecl_args_nim(decl, prefix)}):{funcdecl_res_nim(decl, prefix)} {{.cdecl, importc:\"{decl['name']}\".}}")
nim_func_name = as_camel_case(check_override(c_func_name), prefix, wrap=False)
nim_res_type = funcdecl_result(decl, prefix)
if c_func_name in c_callbacks:
l(f"proc {nim_func_name}*({funcdecl_args_c(decl, prefix)}):{nim_res_type} {{.cdecl, importc:\"{c_func_name}\".}}")
else:
l(f"proc c_{nim_func_name}({funcdecl_args_c(decl, prefix)}):{nim_res_type} {{.cdecl, importc:\"{c_func_name}\".}}")
l(f"proc {wrap_keywords(nim_func_name)}*({funcdecl_args_nim(decl, prefix)}):{nim_res_type} =")
s = f" c_{nim_func_name}("
for i, param_decl in enumerate(decl['params']):
if i > 0:
s += ", "
arg_name = param_decl['name']
arg_type = param_decl['type']
if is_const_struct_ptr(arg_type):
s += f"unsafeAddr({arg_name})"
else:
s += arg_name
s += ")"
l(s)
l("")
def gen_array_converters(decl, prefix):
for field in decl['fields']:
if util.is_array_type(field['type']):
array_type = util.extract_array_type(field['type'])
array_sizes = util.extract_array_sizes(field['type'])
struct_name = as_nim_struct_name(decl, prefix)
field_name = as_nim_field_name(field, prefix, check_private=False)
array_base_type = as_nim_type(array_type, prefix)
if util.is_1d_array_type(field['type']):
n = array_sizes[0]
l(f'converter to{struct_name}{field_name}*[N:static[int]](items: array[N, {array_base_type}]): array[{n}, {array_base_type}] =')
l(f' static: assert(N < {n})')
l(f' for index,item in items.pairs: result[index]=item')
l('')
elif util.is_2d_array_type(field['type']):
x = array_sizes[1]
y = array_sizes[0]
l(f'converter to{struct_name}{field_name}*[Y:static[int], X:static[int]](items: array[Y, array[X, {array_base_type}]]): array[{y}, array[{x}, {array_base_type}]] =')
l(f' static: assert(X < {x})')
l(f' static: assert(Y < {y})')
l(f' for indexY,itemY in items.pairs:')
l(f' for indexX, itemX in itemY.pairs:')
l(f' result[indexY][indexX] = itemX')
l('')
else:
sys.exit('Unsupported converter array dimension (> 2)!')
def pre_parse(inp):
global struct_types
global enum_types
@ -497,15 +479,92 @@ def pre_parse(inp):
elif kind == 'enum':
enum_name = decl['name']
enum_types.append(enum_name)
enum_items[enum_name] = []
for item in decl['items']:
enum_items[enum_name].append(as_enum_item_name(item['name']))
def gen_imports(inp, dep_prefixes):
for dep_prefix in dep_prefixes:
dep_module_name = module_names[dep_prefix]
l(f'import {dep_module_name}')
l('')
def gen_extra(inp):
if inp['prefix'] in ['sg_']:
# FIXME: remove when sokol-shdc has been integrated!
l('when defined gl:')
l(' const gl* = true')
l(' const d3d11* = false')
l(' const metal* = false')
l('elif defined windows:')
l(' const gl* = false')
l(' const d3d11* = true')
l(' const metal* = false')
l('elif defined macosx:')
l(' const gl* = false')
l(' const d3d11* = false')
l(' const metal* = true')
l('elif defined linux:')
l(' const gl* = true')
l(' const d3d11* = false')
l(' const metal* = false')
l('else:')
l(' error("unsupported platform")')
l('')
if inp['prefix'] in ['sg_', 'sapp_']:
l('when defined windows:')
l(' when not defined vcc:')
l(' {.passl:"-lkernel32 -luser32 -lshell32 -lgdi32".}')
l(' when defined gl:')
l(' {.passc:"-DSOKOL_GLCORE33".}')
l(' else:')
l(' {.passc:"-DSOKOL_D3D11".}')
l(' when not defined vcc:')
l(' {.passl:"-ld3d11 -ldxgi".}')
l('elif defined macosx:')
l(' {.passc:"-x objective-c".}')
l(' {.passl:"-framework Cocoa -framework QuartzCore".}')
l(' when defined gl:')
l(' {.passc:"-DSOKOL_GLCORE33".}')
l(' {.passl:"-framework OpenGL".}')
l(' else:')
l(' {.passc:"-DSOKOL_METAL".}')
l(' {.passl:"-framework Metal -framework MetalKit".}')
l('elif defined linux:')
l(' {.passc:"-DSOKOL_GLCORE33".}')
l(' {.passl:"-lX11 -lXi -lXcursor -lGL -lm -ldl -lpthread".}')
l('else:')
l(' error("unsupported platform")')
l('')
if inp['prefix'] in ['saudio_']:
l('when defined windows:')
l(' when not defined vcc:')
l(' {.passl:"-lkernel32 -lole32".}')
l('elif defined macosx:')
l(' {.passl:"-framework AudioToolbox".}')
l('elif defined linux:')
l(' {.passl:"-lasound -lm -lpthread".}')
l('else:')
l(' error("unsupported platform")')
l('')
if inp['prefix'] in ['sg_']:
l('## Convert a 4-element tuple of numbers to a gfx.Color')
l('converter toColor*[R:SomeNumber,G:SomeNumber,B:SomeNumber,A:SomeNumber](rgba: tuple [r:R,g:G,b:B,a:A]):Color =')
l(' Color(r:rgba.r.float32, g:rgba.g.float32, b:rgba.b.float32, a:rgba.a.float32)')
l('')
l('## Convert a 3-element tuple of numbers to a gfx.Color')
l('converter toColor*[R:SomeNumber,G:SomeNumber,B:SomeNumber](rgba: tuple [r:R,g:G,b:B]):Color =')
l(' Color(r:rgba.r.float32, g:rgba.g.float32, b:rgba.b.float32, a:1.float32)')
l('')
# NOTE: this simplistic to_Range() converter has various issues, some of them dangerous:
# - doesn't work as expected for slice types
# - it's very easy to create a range that points to invalid memory
# (so far observed for stack-allocated structs <= 16 bytes)
#if inp['prefix'] in ['sg_', 'sdtx_', 'sshape_']:
# l('# helper function to convert "anything" into a Range')
# l('converter to_Range*[T](source: T): Range =')
# l(' Range(addr: source.unsafeAddr, size: source.sizeof.uint)')
# l('')
c_source_path = '/'.join(c_source_paths[inp['prefix']].split('/')[3:])
l('{.passc:"-DSOKOL_NIM_IMPL".}')
l(f'{{.compile:"{c_source_path}".}}')
def gen_module(inp, dep_prefixes):
l('## machine generated, do not edit')
@ -518,22 +577,27 @@ def gen_module(inp, dep_prefixes):
kind = decl['kind']
if kind == 'consts':
gen_consts(decl, prefix)
elif kind == 'enum':
gen_enum(decl, prefix)
elif kind == 'struct':
gen_struct(decl, prefix)
elif kind == 'func':
if not check_func_name_ignore(decl['name']):
elif not check_ignore(decl['name']):
if kind == 'struct':
gen_struct(decl, prefix)
gen_array_converters(decl, prefix)
elif kind == 'enum':
gen_enum(decl, prefix)
elif kind == 'func':
gen_func_nim(decl, prefix)
gen_extra(inp)
def prepare():
print('Generating nim bindings:')
print('=== Generating Nim bindings:')
if not os.path.isdir('sokol-nim/src/sokol'):
os.makedirs('sokol-nim/src/sokol')
if not os.path.isdir('sokol-nim/src/sokol/c'):
os.makedirs('sokol-nim/src/sokol/c')
def gen(c_header_path, c_prefix, dep_c_prefixes):
if not c_prefix in module_names:
print(f' >> warning: skipping generation for {c_prefix} prefix...')
return
global out_lines
module_name = module_names[c_prefix]
c_source_path = c_source_paths[c_prefix]
@ -543,20 +607,5 @@ def gen(c_header_path, c_prefix, dep_c_prefixes):
ir = gen_ir.gen(c_header_path, c_source_path, module_name, c_prefix, dep_c_prefixes)
gen_module(ir, dep_c_prefixes)
output_path = f"sokol-nim/src/sokol/{ir['module']}.nim"
## some changes for readability
out_lines = out_lines.replace("PixelformatInfo", "PixelFormatInfo")
out_lines = out_lines.replace(" Dontcare,", " DontCare,")
out_lines = out_lines.replace(" Vertexbuffer,", " VertexBuffer,")
out_lines = out_lines.replace(" Indexbuffer,", " IndexBuffer,")
out_lines = out_lines.replace(" N2d,", " Plane,")
out_lines = out_lines.replace(" N3d,", " Volume,")
out_lines = out_lines.replace(" Vs,", " Vertex,")
out_lines = out_lines.replace(" Fs,", " Fragment,")
## include extensions in generated code
l("# Nim-specific API extensions")
l(f"include nim/{ir['module']}")
with open(output_path, 'w', newline='\n') as f_outp:
f_outp.write(out_lines)

View File

@ -1,5 +1,5 @@
#-------------------------------------------------------------------------------
# Read output of gen_json.py and generate Zig language bindings.
# Generate Zig bindings.
#
# Zig coding style:
# - types are PascalCase
@ -7,9 +7,12 @@
# - otherwise snake_case
#-------------------------------------------------------------------------------
import gen_ir
import json, re, os, shutil
import os, shutil, sys
import gen_util as util
module_names = {
'slog_': 'log',
'sg_': 'gfx',
'sapp_': 'app',
'stm_': 'time',
@ -20,6 +23,7 @@ module_names = {
}
c_source_paths = {
'slog_': 'sokol-zig/src/sokol/c/sokol_log.c',
'sg_': 'sokol-zig/src/sokol/c/sokol_gfx.c',
'sapp_': 'sokol-zig/src/sokol/c/sokol_app.c',
'stm_': 'sokol-zig/src/sokol/c/sokol_time.c',
@ -29,21 +33,23 @@ c_source_paths = {
'sshape_': 'sokol-zig/src/sokol/c/sokol_shape.c',
}
name_ignores = [
ignores = [
'sdtx_printf',
'sdtx_vprintf',
'sg_install_trace_hooks',
'sg_trace_hooks',
]
name_overrides = {
'sgl_error': 'sgl_get_error', # 'error' is reserved in Zig
'sgl_deg': 'sgl_as_degrees',
'sgl_rad': 'sgl_as_radians'
}
# functions that need to be exposed as 'raw' C callbacks without a Zig wrapper function
c_callbacks = [
'slog_func'
]
# NOTE: syntax for function results: "func_name.RESULT"
type_overrides = {
overrides = {
'sgl_error': 'sgl_get_error', # 'error' is reserved in Zig
'sgl_deg': 'sgl_as_degrees',
'sgl_rad': 'sgl_as_radians',
'sg_context_desc.color_format': 'int',
'sg_context_desc.depth_format': 'int',
'sg_apply_uniforms.ub_index': 'uint32_t',
@ -53,6 +59,7 @@ type_overrides = {
'sshape_element_range_t.base_element': 'uint32_t',
'sshape_element_range_t.num_elements': 'uint32_t',
'sdtx_font.font_index': 'uint32_t',
'SGL_NO_ERROR': 'SGL_ERROR_NO_ERROR',
}
prim_types = {
@ -92,6 +99,7 @@ prim_defaults = {
'size_t': '0'
}
struct_types = []
enum_types = []
enum_items = {}
@ -107,9 +115,6 @@ def reset_globals():
enum_items = {}
out_lines = ''
re_1d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]$")
re_2d_array = re.compile("^(?:const )?\w*\s\*?\[\d*\]\[\d*\]$")
def l(s):
global out_lines
out_lines += s + '\n'
@ -122,6 +127,7 @@ def as_zig_struct_type(s, prefix):
parts = s.lower().split('_')
outp = '' if s.startswith(prefix) else f'{parts[0]}.'
for part in parts[1:]:
# ignore '_t' type postfix
if (part != 't'):
outp += part.capitalize()
return outp
@ -135,42 +141,20 @@ def as_zig_enum_type(s, prefix):
outp += part.capitalize()
return outp
def check_type_override(func_or_struct_name, field_or_arg_name, orig_type):
s = f"{func_or_struct_name}.{field_or_arg_name}"
if s in type_overrides:
return type_overrides[s]
else:
return orig_type
def check_name_override(name):
if name in name_overrides:
return name_overrides[name]
else:
def check_override(name, default=None):
if name in overrides:
return overrides[name]
elif default is None:
return name
else:
return default
def check_name_ignore(name):
return name in name_ignores
# PREFIX_BLA_BLUB to bla_blub
def as_snake_case(s, prefix):
outp = s.lower()
if outp.startswith(prefix):
outp = outp[len(prefix):]
return outp
# prefix_bla_blub => blaBlub
def as_camel_case(s):
parts = s.lower().split('_')[1:]
outp = parts[0]
for part in parts[1:]:
outp += part.capitalize()
return outp
def check_ignore(name):
return name in ignores
# PREFIX_ENUM_BLA => Bla, _PREFIX_ENUM_BLA => Bla
def as_enum_item_name(s):
outp = s
if outp.startswith('_'):
outp = outp[1:]
outp = s.lstrip('_')
parts = outp.split('_')[2:]
outp = '_'.join(parts)
if outp[0].isdigit():
@ -189,15 +173,6 @@ def is_struct_type(s):
def is_enum_type(s):
return s in enum_types
def is_string_ptr(s):
return s == "const char *"
def is_const_void_ptr(s):
return s == "const void *"
def is_void_ptr(s):
return s == "void *"
def is_const_prim_ptr(s):
for prim_type in prim_types:
if s == f"const {prim_type} *":
@ -216,32 +191,10 @@ def is_const_struct_ptr(s):
return True
return False
def is_func_ptr(s):
return '(*)' in s
def is_1d_array_type(s):
return re_1d_array.match(s)
def is_2d_array_type(s):
return re_2d_array.match(s)
def type_default_value(s):
return prim_defaults[s]
def extract_array_type(s):
return s[:s.index('[')].strip()
def extract_array_nums(s):
return s[s.index('['):].replace('[', ' ').replace(']', ' ').split()
def extract_ptr_type(s):
tokens = s.split()
if tokens[0] == 'const':
return tokens[1]
else:
return tokens[0]
def as_extern_c_arg_type(arg_type, prefix):
def as_c_arg_type(arg_type, prefix):
if arg_type == "void":
return "void"
elif is_prim_type(arg_type):
@ -250,20 +203,20 @@ def as_extern_c_arg_type(arg_type, prefix):
return as_zig_struct_type(arg_type, prefix)
elif is_enum_type(arg_type):
return as_zig_enum_type(arg_type, prefix)
elif is_void_ptr(arg_type):
return "?*c_void"
elif is_const_void_ptr(arg_type):
return "?*const c_void"
elif is_string_ptr(arg_type):
elif util.is_void_ptr(arg_type):
return "?*anyopaque"
elif util.is_const_void_ptr(arg_type):
return "?*const anyopaque"
elif util.is_string_ptr(arg_type):
return "[*c]const u8"
elif is_const_struct_ptr(arg_type):
return f"[*c]const {as_zig_struct_type(extract_ptr_type(arg_type), prefix)}"
return f"[*c]const {as_zig_struct_type(util.extract_ptr_type(arg_type), prefix)}"
elif is_prim_ptr(arg_type):
return f"[*c] {as_zig_prim_type(extract_ptr_type(arg_type))}"
return f"[*c] {as_zig_prim_type(util.extract_ptr_type(arg_type))}"
elif is_const_prim_ptr(arg_type):
return f"[*c]const {as_zig_prim_type(extract_ptr_type(arg_type))}"
return f"[*c]const {as_zig_prim_type(util.extract_ptr_type(arg_type))}"
else:
return '??? (as_extern_c_arg_type)'
sys.exit(f"Error as_c_arg_type(): {arg_type}")
def as_zig_arg_type(arg_prefix, arg_type, prefix):
# NOTE: if arg_prefix is None, the result is used as return value
@ -279,21 +232,24 @@ def as_zig_arg_type(arg_prefix, arg_type, prefix):
return pre + as_zig_struct_type(arg_type, prefix)
elif is_enum_type(arg_type):
return pre + as_zig_enum_type(arg_type, prefix)
elif is_void_ptr(arg_type):
return pre + "?*c_void"
elif is_const_void_ptr(arg_type):
return pre + "?*const c_void"
elif is_string_ptr(arg_type):
elif util.is_void_ptr(arg_type):
return pre + "?*anyopaque"
elif util.is_const_void_ptr(arg_type):
return pre + "?*const anyopaque"
elif util.is_string_ptr(arg_type):
return pre + "[:0]const u8"
elif is_const_struct_ptr(arg_type):
# not a bug, pass const structs by value
return pre + f"{as_zig_struct_type(extract_ptr_type(arg_type), prefix)}"
return pre + f"{as_zig_struct_type(util.extract_ptr_type(arg_type), prefix)}"
elif is_prim_ptr(arg_type):
return pre + f"* {as_zig_prim_type(extract_ptr_type(arg_type))}"
return pre + f"* {as_zig_prim_type(util.extract_ptr_type(arg_type))}"
elif is_const_prim_ptr(arg_type):
return pre + f"*const {as_zig_prim_type(extract_ptr_type(arg_type))}"
return pre + f"*const {as_zig_prim_type(util.extract_ptr_type(arg_type))}"
else:
return arg_prefix + "??? (as_zig_arg_type)"
sys.exit(f"ERROR as_zig_arg_type(): {arg_type}")
def is_zig_string(zig_type):
return zig_type == "[:0]const u8"
# get C-style arguments of a function pointer as string
def funcptr_args_c(field_type, prefix):
@ -303,22 +259,24 @@ def funcptr_args_c(field_type, prefix):
arg_type = token.strip()
if s != "":
s += ", "
c_arg = as_extern_c_arg_type(arg_type, prefix)
if (c_arg == "void"):
c_arg = as_c_arg_type(arg_type, prefix)
if c_arg == "void":
return ""
else:
s += c_arg
return s
# get C-style result of a function pointer as string
def funcptr_res_c(field_type):
def funcptr_result_c(field_type):
res_type = field_type[:field_type.index('(*)')].strip()
if res_type == 'void':
return 'void'
elif is_const_void_ptr(res_type):
return '?*const c_void'
elif util.is_const_void_ptr(res_type):
return '?*const anyopaque'
elif util.is_void_ptr(res_type):
return '?*anyopaque'
else:
return '???'
sys.exit(f"ERROR funcptr_result_c(): {field_type}")
def funcdecl_args_c(decl, prefix):
s = ""
@ -327,8 +285,8 @@ def funcdecl_args_c(decl, prefix):
if s != "":
s += ", "
param_name = param_decl['name']
param_type = check_type_override(func_name, param_name, param_decl['type'])
s += as_extern_c_arg_type(param_type, prefix)
param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
s += as_c_arg_type(param_type, prefix)
return s
def funcdecl_args_zig(decl, prefix):
@ -338,55 +296,49 @@ def funcdecl_args_zig(decl, prefix):
if s != "":
s += ", "
param_name = param_decl['name']
param_type = check_type_override(func_name, param_name, param_decl['type'])
param_type = check_override(f'{func_name}.{param_name}', default=param_decl['type'])
s += f"{as_zig_arg_type(f'{param_name}: ', param_type, prefix)}"
return s
def funcdecl_result_c(decl, prefix):
func_name = decl['name']
decl_type = decl['type']
result_type = check_type_override(func_name, 'RESULT', decl_type[:decl_type.index('(')].strip())
return as_extern_c_arg_type(result_type, prefix)
result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip())
return as_c_arg_type(result_type, prefix)
def funcdecl_result_zig(decl, prefix):
func_name = decl['name']
decl_type = decl['type']
result_type = check_type_override(func_name, 'RESULT', decl_type[:decl_type.index('(')].strip())
result_type = check_override(f'{func_name}.RESULT', default=decl_type[:decl_type.index('(')].strip())
zig_res_type = as_zig_arg_type(None, result_type, prefix)
if zig_res_type == "":
zig_res_type = "void"
return zig_res_type
def gen_struct(decl, prefix, callconvc_funcptrs = True, use_raw_name=False, use_extern=True):
struct_name = decl['name']
zig_type = struct_name if use_raw_name else as_zig_struct_type(struct_name, prefix)
l(f"pub const {zig_type} = {'extern ' if use_extern else ''}struct {{")
def gen_struct(decl, prefix):
struct_name = check_override(decl['name'])
zig_type = as_zig_struct_type(struct_name, prefix)
l(f"pub const {zig_type} = extern struct {{")
for field in decl['fields']:
field_name = field['name']
field_type = field['type']
field_type = check_type_override(struct_name, field_name, field_type)
field_name = check_override(field['name'])
field_type = check_override(f'{struct_name}.{field_name}', default=field['type'])
if is_prim_type(field_type):
l(f" {field_name}: {as_zig_prim_type(field_type)} = {type_default_value(field_type)},")
elif is_struct_type(field_type):
l(f" {field_name}: {as_zig_struct_type(field_type, prefix)} = .{{ }},")
elif is_enum_type(field_type):
l(f" {field_name}: {as_zig_enum_type(field_type, prefix)} = .{enum_default_item(field_type)},")
elif is_string_ptr(field_type):
elif util.is_string_ptr(field_type):
l(f" {field_name}: [*c]const u8 = null,")
elif is_const_void_ptr(field_type):
l(f" {field_name}: ?*const c_void = null,")
elif is_void_ptr(field_type):
l(f" {field_name}: ?*c_void = null,")
elif util.is_const_void_ptr(field_type):
l(f" {field_name}: ?*const anyopaque = null,")
elif util.is_void_ptr(field_type):
l(f" {field_name}: ?*anyopaque = null,")
elif is_const_prim_ptr(field_type):
l(f" {field_name}: ?[*]const {as_zig_prim_type(extract_ptr_type(field_type))} = null,")
elif is_func_ptr(field_type):
if callconvc_funcptrs:
l(f" {field_name}: ?fn({funcptr_args_c(field_type, prefix)}) callconv(.C) {funcptr_res_c(field_type)} = null,")
else:
l(f" {field_name}: ?fn({funcptr_args_c(field_type, prefix)}) {funcptr_res_c(field_type)} = null,")
elif is_1d_array_type(field_type):
array_type = extract_array_type(field_type)
array_nums = extract_array_nums(field_type)
l(f" {field_name}: ?[*]const {as_zig_prim_type(util.extract_ptr_type(field_type))} = null,")
elif util.is_func_ptr(field_type):
l(f" {field_name}: ?*const fn({funcptr_args_c(field_type, prefix)}) callconv(.C) {funcptr_result_c(field_type)} = null,")
elif util.is_1d_array_type(field_type):
array_type = util.extract_array_type(field_type)
array_sizes = util.extract_array_sizes(field_type)
if is_prim_type(array_type) or is_struct_type(array_type):
if is_prim_type(array_type):
zig_type = as_zig_prim_type(array_type)
@ -398,19 +350,17 @@ def gen_struct(decl, prefix, callconvc_funcptrs = True, use_raw_name=False, use_
zig_type = as_zig_enum_type(array_type, prefix)
def_val = '.{}'
else:
zig_type = '??? (array type)'
def_val = '???'
t0 = f"[{array_nums[0]}]{zig_type}"
t0_slice = f"[]const {zig_type}"
sys.exit(f"ERROR gen_struct is_1d_array_type: {array_type}")
t0 = f"[{array_sizes[0]}]{zig_type}"
t1 = f"[_]{zig_type}"
l(f" {field_name}: {t0} = {t1}{{{def_val}}} ** {array_nums[0]},")
elif is_const_void_ptr(array_type):
l(f" {field_name}: [{array_nums[0]}]?*const c_void = [_]?*const c_void {{ null }} ** {array_nums[0]},")
l(f" {field_name}: {t0} = {t1}{{{def_val}}} ** {array_sizes[0]},")
elif util.is_const_void_ptr(array_type):
l(f" {field_name}: [{array_sizes[0]}]?*const anyopaque = [_]?*const anyopaque {{ null }} ** {array_sizes[0]},")
else:
l(f"// FIXME: ??? array {field_name}: {field_type} => {array_type} [{array_nums[0]}]")
elif is_2d_array_type(field_type):
array_type = extract_array_type(field_type)
array_nums = extract_array_nums(field_type)
sys.exit(f"ERROR gen_struct: array {field_name}: {field_type} => {array_type} [{array_sizes[0]}]")
elif util.is_2d_array_type(field_type):
array_type = util.extract_array_type(field_type)
array_sizes = util.extract_array_sizes(field_type)
if is_prim_type(array_type):
zig_type = as_zig_prim_type(array_type)
def_val = type_default_value(array_type)
@ -418,22 +368,23 @@ def gen_struct(decl, prefix, callconvc_funcptrs = True, use_raw_name=False, use_
zig_type = as_zig_struct_type(array_type, prefix)
def_val = ".{ }"
else:
zig_type = "???"
def_val = "???"
t0 = f"[{array_nums[0]}][{array_nums[1]}]{zig_type}"
l(f" {field_name}: {t0} = [_][{array_nums[1]}]{zig_type}{{[_]{zig_type}{{ {def_val} }}**{array_nums[1]}}}**{array_nums[0]},")
sys.exit(f"ERROR gen_struct is_2d_array_type: {array_type}")
t0 = f"[{array_sizes[0]}][{array_sizes[1]}]{zig_type}"
l(f" {field_name}: {t0} = [_][{array_sizes[1]}]{zig_type}{{[_]{zig_type}{{ {def_val} }}**{array_sizes[1]}}}**{array_sizes[0]},")
else:
l(f"// FIXME: {field_name}: {field_type};")
sys.exit(f"ERROR gen_struct: {field_name}: {field_type};")
l("};")
def gen_consts(decl, prefix):
for item in decl['items']:
l(f"pub const {as_snake_case(item['name'], prefix)} = {item['value']};")
item_name = check_override(item['name'])
l(f"pub const {util.as_lower_snake_case(item_name, prefix)} = {item['value']};")
def gen_enum(decl, prefix):
l(f"pub const {as_zig_enum_type(decl['name'], prefix)} = enum(i32) {{")
enum_name = check_override(decl['name'])
l(f"pub const {as_zig_enum_type(enum_name, prefix)} = enum(i32) {{")
for item in decl['items']:
item_name = as_enum_item_name(item['name'])
item_name = as_enum_item_name(check_override(item['name']))
if item_name != "FORCE_U32":
if 'value' in item:
l(f" {item_name} = {item['value']},")
@ -446,27 +397,36 @@ def gen_func_c(decl, prefix):
def gen_func_zig(decl, prefix):
c_func_name = decl['name']
zig_func_name = as_camel_case(check_name_override(decl['name']))
zig_res_type = funcdecl_result_zig(decl, prefix)
l(f"pub fn {zig_func_name}({funcdecl_args_zig(decl, prefix)}) {zig_res_type} {{")
if zig_res_type != 'void':
s = f" return {c_func_name}("
zig_func_name = util.as_lower_camel_case(check_override(decl['name']), prefix)
if c_func_name in c_callbacks:
# a simple forwarded C callback function
l(f"pub const {zig_func_name} = {c_func_name};")
else:
s = f" {c_func_name}("
for i, param_decl in enumerate(decl['params']):
if i > 0:
s += ", "
arg_name = param_decl['name']
arg_type = param_decl['type']
if is_const_struct_ptr(arg_type):
s += f"&{arg_name}"
elif is_string_ptr(arg_type):
s += f"@ptrCast([*c]const u8,{arg_name})"
zig_res_type = funcdecl_result_zig(decl, prefix)
l(f"pub fn {zig_func_name}({funcdecl_args_zig(decl, prefix)}) {zig_res_type} {{")
if is_zig_string(zig_res_type):
# special case: convert C string to Zig string slice
s = f" return cStrToZig({c_func_name}("
elif zig_res_type != 'void':
s = f" return {c_func_name}("
else:
s += arg_name
s += ");"
l(s)
l("}")
s = f" {c_func_name}("
for i, param_decl in enumerate(decl['params']):
if i > 0:
s += ", "
arg_name = param_decl['name']
arg_type = param_decl['type']
if is_const_struct_ptr(arg_type):
s += f"&{arg_name}"
elif util.is_string_ptr(arg_type):
s += f"@ptrCast([*c]const u8,{arg_name})"
else:
s += arg_name
if is_zig_string(zig_res_type):
s += ")"
s += ");"
l(s)
l("}")
def pre_parse(inp):
global struct_types
@ -483,12 +443,17 @@ def pre_parse(inp):
enum_items[enum_name].append(as_enum_item_name(item['name']))
def gen_imports(inp, dep_prefixes):
l('const builtin = @import("builtin");')
for dep_prefix in dep_prefixes:
dep_module_name = module_names[dep_prefix]
l(f'const {dep_prefix[:-1]} = @import("{dep_module_name}.zig");')
l('')
l('')
def gen_helpers(inp):
l('// helper function to convert a C string to a Zig string slice')
l('fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {')
l(' return @import("std").mem.span(c_str);')
l('}')
if inp['prefix'] in ['sg_', 'sdtx_', 'sshape_']:
l('// helper function to convert "anything" to a Range struct')
l('pub fn asRange(val: anytype) Range {')
@ -502,7 +467,7 @@ def gen_helpers(inp):
l(' }')
l(' },')
l(' .Struct, .Array => {')
l(' return .{ .ptr = &val, .size = @sizeOf(@TypeOf(val)) };')
l(' @compileError("Structs and arrays must be passed as pointers to asRange");')
l(' },')
l(' else => {')
l(' @compileError("Cannot convert to range!");')
@ -547,7 +512,7 @@ def gen_module(inp, dep_prefixes):
kind = decl['kind']
if kind == 'consts':
gen_consts(decl, prefix)
elif not check_name_ignore(decl['name']):
elif not check_ignore(decl['name']):
if kind == 'struct':
gen_struct(decl, prefix)
elif kind == 'enum':
@ -557,13 +522,16 @@ def gen_module(inp, dep_prefixes):
gen_func_zig(decl, prefix)
def prepare():
print('Generating zig bindings:')
print('=== Generating Zig bindings:')
if not os.path.isdir('sokol-zig/src/sokol'):
os.makedirs('sokol-zig/src/sokol')
if not os.path.isdir('sokol-zig/src/sokol/c'):
os.makedirs('sokol-zig/src/sokol/c')
def gen(c_header_path, c_prefix, dep_c_prefixes):
if not c_prefix in module_names:
print(f' >> warning: skipping generation for {c_prefix} prefix...')
return
module_name = module_names[c_prefix]
c_source_path = c_source_paths[c_prefix]
print(f' {c_header_path} => {module_name}')

View File

@ -9,6 +9,7 @@
#define SOKOL_IMPL
#define SOKOL_GLCORE33
#include "sokol_gfx.h"
#include "sokol_log.h"
#include "sokol_time.h"
#define SOKOL_GL_IMPL
#include "util/sokol_gl.h"
@ -28,6 +29,7 @@ const int MaxVertices = (1 << 16);
const int MaxIndices = MaxVertices * 3;
uint64_t last_time = 0;
const int cMSAASampleCount = 8;
sg_pass_action pass_action;
sg_pipeline pip;
@ -42,7 +44,6 @@ static void draw_imgui(ImDrawData*);
#define HANDMADE_MATH_IMPLEMENTATION
#define HANDMADE_MATH_NO_SSE
#include "HandmadeMath.h"
#include "camera.h"
// ozz-animation headers
#include <cmath> // fmodf
@ -77,7 +78,7 @@ static struct {
struct {
double frame;
double anim_update_time;
float absolute;
double absolute;
uint64_t laptime;
float factor;
float anim_ratio;
@ -107,6 +108,7 @@ struct Viewport {
sg_pass_action pass_action = {};
sg_pass pass = {};
sg_pipeline pip = {};
sgl_pipeline glpip = {};
sg_bindings bind = {};
sg_image color_image = {};
sg_image depth_image = {};
@ -138,7 +140,7 @@ struct Viewport {
.width = this->size[0],
.height = this->size[1],
.pixel_format = SG_PIXELFORMAT_RGBA8,
.sample_count = 4,
.sample_count = cMSAASampleCount,
.min_filter = SG_FILTER_LINEAR,
.mag_filter = SG_FILTER_LINEAR,
.wrap_u = SG_WRAP_REPEAT,
@ -155,6 +157,14 @@ struct Viewport {
offscreen_pass_desc.label = "offscreen-pass";
this->pass = sg_make_pass(&offscreen_pass_desc);
sg_pipeline_desc gl_pipeline_desc = {
.depth = {
.compare = SG_COMPAREFUNC_LESS_EQUAL,
.write_enabled = true
},
.cull_mode = SG_CULLMODE_BACK
};
}
};
@ -350,11 +360,8 @@ ApplicationConfig gApplicationConfig;
static uint8_t skel_data_buffer[4 * 1024];
static uint8_t anim_data_buffer[32 * 1024];
static void draw_grid(void);
static void draw_ui(void);
// static void skeleton_data_loaded(const sfetch_response_t* response);
// static void animation_data_loaded(const sfetch_response_t* response);
static void frame(void);
static void draw_grid();
static void frame();
void handle_mouse(GLFWwindow* w, GuiInputState* io_input_state) {
if (!glfwGetWindowAttrib(w, GLFW_FOCUSED)) {
@ -414,6 +421,19 @@ void save_application_config(const char* filename) {
output_file.close();
}
void sokol_logger(
const char* tag,
uint32_t log_level,
uint32_t log_item_id,
const char*
message_or_null, // a message string, may be nullptr in release mode
uint32_t line_nr, // line number in sokol_gl.h
const char*
filename_or_null, // source filename, may be nullptr in release mode
void* user_data) {
fprintf(stderr, "%s\n", message_or_null);
}
int main() {
// window and GL context via GLFW and flextGL
glfwInit();
@ -423,7 +443,8 @@ int main() {
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE);
glfwWindowHint(GLFW_SAMPLES, 16);
GLFWwindow* w = glfwCreateWindow(Width, Height, "ATP Editor", 0, 0);
GLFWwindow* w =
glfwCreateWindow(Width, Height, "ATP Editor", nullptr, nullptr);
glfwMakeContextCurrent(w);
glfwSwapInterval(1);
@ -476,16 +497,19 @@ int main() {
// setup sokol_gfx and sokol_time
stm_setup();
sg_desc desc = {};
sg_desc desc = {.logger = {.func = slog_func}};
sg_setup(&desc);
assert(sg_isvalid());
// setup sokol-gl
sgl_desc_t sgldesc = {
.sample_count = 4
};
.sample_count = cMSAASampleCount,
.logger = sokol_logger};
sgl_setup(&sgldesc);
// sgl_context_desc_t sgl_context_desc = {};
// sgl_context ctx = sgl_make_context(&sgl_context_desc);
SkinnedMeshResource skinned_mesh_resource;
skinned_mesh_resource.loadFromFile("../media/SampleSkinnedMesh.json");
@ -602,6 +626,7 @@ int main() {
pip_desc.colors[0].blend.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA;
pip_desc.colors[0].blend.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
pip_desc.colors[0].write_mask = SG_COLORMASK_RGB;
pip_desc.label = "imgui-rendering";
pip = sg_make_pipeline(&pip_desc);
// initial clear color
@ -625,7 +650,7 @@ int main() {
if (state.ozz.animation != nullptr) {
state.time.absolute =
fmodf(state.time.absolute, state.ozz.animation->duration());
fmod(state.time.absolute, state.ozz.animation->duration());
}
// Update window state
@ -643,7 +668,6 @@ int main() {
glfwGetFramebufferSize(w, &cur_width, &cur_height);
// this is standard ImGui demo code
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(float(cur_width), float(cur_height));
io.DeltaTime = (float)stm_sec(stm_laptime(&last_time));
ImGui::NewFrame();
@ -664,7 +688,7 @@ int main() {
&state.camera,
offscreen_viewport.size[0],
offscreen_viewport.size[1],
state.time.frame,
static_cast<float>(state.time.frame),
0,
0,
nullptr);
@ -703,7 +727,7 @@ int main() {
&state.camera,
cur_width,
cur_height,
state.time.frame,
static_cast<float>(state.time.frame),
gGuiInputState.mousedX,
gGuiInputState.mousedY,
camera_accel);
@ -769,22 +793,22 @@ int main() {
if (gApplicationConfig.viewport_widget.visible) {
ImGui::SetNextWindowPos(
ImVec2(
gApplicationConfig.viewport_widget.position[0],
gApplicationConfig.viewport_widget.position[1]),
static_cast<float>(
gApplicationConfig.viewport_widget.position[0]),
static_cast<float>(
gApplicationConfig.viewport_widget.position[1])),
ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(
ImVec2(
gApplicationConfig.viewport_widget.size[0],
gApplicationConfig.viewport_widget.size[1]),
static_cast<float>(gApplicationConfig.viewport_widget.size[0]),
static_cast<float>(gApplicationConfig.viewport_widget.size[1])),
ImGuiCond_FirstUseEver);
ImGui::Begin("Viewport", &gApplicationConfig.viewport_widget.visible);
ImVec2 viewport_widget_size = ImGui::GetWindowSize();
gApplicationConfig.viewport_widget.size[0] =
viewport_widget_size.x;
gApplicationConfig.viewport_widget.size[1] =
viewport_widget_size.y;
gApplicationConfig.viewport_widget.size[0] = viewport_widget_size.x;
gApplicationConfig.viewport_widget.size[1] = viewport_widget_size.y;
ImGui::Text(
"Viewport size: %d, %d",
@ -795,15 +819,15 @@ int main() {
int* current_size = offscreen_viewport.size;
if (current_size[0] != content_size[0] || current_size[1] != content_size[1]) {
if (current_size[0] != content_size[0]
|| current_size[1] != content_size[1]
|| offscreen_viewport.pass.id == 0) {
offscreen_viewport.Resize(content_size[0], content_size[1]);
}
ImGui::Image(
(ImTextureID)(uintptr_t)offscreen_viewport.color_image.id,
ImVec2(
offscreen_viewport.size[0],
offscreen_viewport.size[1]),
ImVec2(offscreen_viewport.size[0], offscreen_viewport.size[1]),
ImVec2(0.0f, 1.0f),
ImVec2(1.0f, 0.0f));
@ -927,13 +951,16 @@ int main() {
if (state.ozz.animation != nullptr) {
ImGui::SameLine();
ImGui::SliderFloat(
"Time",
&state.time.absolute,
0,
state.ozz.animation->duration(),
"%.3f",
0);
float time_absolute_float = static_cast<float>(state.time.absolute);
if (ImGui::SliderFloat(
"Time",
&time_absolute_float,
0,
state.ozz.animation->duration(),
"%.3f",
0)) {
state.time.absolute = time_absolute_float;
}
}
ImGui::End();
@ -1012,8 +1039,9 @@ int main() {
ImGui::ShowDemoWindow();
}
// Rendering of the offscreen scene
sgl_defaults();
sg_begin_pass(offscreen_viewport.pass, &offscreen_viewport.pass_action);
sgl_load_pipeline(offscreen_viewport.glpip);
sgl_draw();
sg_end_pass();
@ -1023,6 +1051,9 @@ int main() {
draw_imgui(ImGui::GetDrawData());
sg_end_pass();
sg_commit();
glfwSwapBuffers(w);
glfwPollEvents();