feat: enhance build system and documentation for SDK installation and shader support

- Updated .gitignore to include new build and output directories for SDK packaging.
- Modified CMakeLists.txt to include installation script for SDK.
- Enhanced Makefile to support new SDK targets for various platforms.
- Improved documentation to reflect new shader capabilities and 3D model support.
- Added new shader functions and updated rendering systems to utilize custom shaders.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
sunxfancyandCursor committed 2026-08-06 21:08:47 +08:00
1 parent 847e14b67a
commit 8e0221c552
66 files changed
+14147 -200

No files matched your search

+2
View File
@@ -1,9 +1,11 @@
build*/
dist/
third-party/
third-party-binary/
.history/
docs/superpowers/
.worktrees/
examples/native-plugin/build/
platform/android/apk/local.properties
platform/android/apk/.gradle/
platform/android/apk/app/build/
+2
View File
@@ -62,6 +62,8 @@ function(main)
if(BUILD_TESTING)
add_subdirectory(test)
endif()
include(${CMAKE_SOURCE_DIR}/cmake/install_sdk.cmake)
endfunction()
+33 -4
View File
@@ -104,6 +104,8 @@ GAME ?= example
run/win32-debug run/linux-debug run/macosx-debug \
sync/android-libs sync/android-assets install/android-debug run/android-debug log/android \
install/ios-debug run/ios-debug log/ios \
sdk/win32 sdk/linux sdk/macosx sdk/android sdk/ios \
sdk/win32-debug sdk/linux-debug sdk/macosx-debug sdk/android-debug sdk/ios-debug \
reinstall/third-party reinstall/third-party/win32 reinstall/third-party/win32-debug \
reinstall/third-party/linux reinstall/third-party/linux-debug \
reinstall/third-party/macosx reinstall/third-party/macosx-debug \
@@ -124,6 +126,7 @@ show-targets:
@echo "all -> $(ALL_DEBUG_TARGETS)"
@echo "debug -> build/$(PLATFORM)-debug"
@echo "release -> build/$(PLATFORM)"
@echo "sdk -> sdk/$(PLATFORM) (Release) or sdk/$(PLATFORM)-debug"
@echo "run -> run/$(PLATFORM)-debug (GAME=$(GAME))"
# clangd: build/compile_commands.json -> host platform debug CDB
@@ -143,19 +146,25 @@ wsl/linux-debug:
wsl/linux:
wsl --cd "$(CURDIR)" -- make build/linux
# win32: Ninja/MSVC helpers(debug 用 Ninja+cl;Release VS 生成器也可借 vcvars)
WITH_MSVC = cmake\with-msvc.cmd
build/win32: build/win32/EVEngine.sln
cmake.exe --build $@ --config Release --target deps -j 32
cmake.exe --build $@ --config Release -j 32
build/win32/EVEngine.sln:
cmake.exe -G "Visual Studio 18 2026" -DCMAKE_BUILD_TYPE=Release -A x64 -B build/win32 -S .
build/linux: build/linux/Makefile
cmake --build $@ --target deps -j 32
cmake --build $@ -j 32
build/linux/Makefile:
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Release -DBUILD_PLATFORM=linux -B build/linux -S .
build/macosx: build/macosx/Makefile
cmake --build $@ --target deps -j 32
cmake --build $@ -j 32
build/macosx/Makefile:
@@ -179,23 +188,22 @@ build/android/build.ninja:
-DBUILD_TESTING=OFF \
-B build/android -S .
# win32-debug: Ninja + MSVC cl;via cmake/with-msvc.cmd (vcvars) so STL headers resolve
# Emits build/win32-debug/compile_commands.json with MSVC INCLUDE paths for clangd
WITH_MSVC = cmake\with-msvc.cmd
build/win32-debug: build/win32-debug/build.ninja
$(WITH_MSVC) cmake.exe --build $@ --target deps -j 32
$(WITH_MSVC) cmake.exe --build $@ -j 32
build/win32-debug/build.ninja:
$(WITH_MSVC) cmake.exe -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -B build/win32-debug -S .
build/linux-debug: build/linux-debug/Makefile
cmake --build $@ --target deps -j 32
cmake --build $@ -j 32
build/linux-debug/Makefile:
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug -DBUILD_PLATFORM=linux -B build/linux-debug -S .
build/macosx-debug: build/macosx-debug/Makefile
cmake --build $@ --target deps -j 32
cmake --build $@ -j 32
build/macosx-debug/Makefile:
@@ -220,6 +228,7 @@ build/android-debug/build.ninja:
-B build/android-debug -S .
build/ios: build/ios/EVEngine.xcodeproj
cmake --build build/ios --target deps -j 8
@if [ -z "$(IOS_DEVELOPMENT_TEAM)" ]; then \
echo "WARNING: IOS_DEVELOPMENT_TEAM unset; building unsigned (install will fail)"; \
cd build/ios && xcodebuild -scheme eve -configuration Release \
@@ -444,6 +453,26 @@ run/linux:
run/macosx:
cd $(GAME) && ../build/macosx/src/engine/eve run
# Target-platform SDK install (independent prefix per plat; for publishing games TO that plat).
# Release: make sdk/macosx → builds build/macosx then dist/eve-sdk/macosx
# Debug: make sdk/macosx-debug
sdk/win32: build/win32
sdk/linux: build/linux
sdk/macosx: build/macosx
sdk/android: build/android
sdk/ios: build/ios
sdk/win32-debug: build/win32-debug
sdk/linux-debug: build/linux-debug
sdk/macosx-debug: build/macosx-debug
sdk/android-debug: build/android-debug
sdk/ios-debug: build/ios-debug
sdk/win32 sdk/linux sdk/macosx sdk/android sdk/ios \
sdk/win32-debug sdk/linux-debug sdk/macosx-debug sdk/android-debug sdk/ios-debug:
@plat=$@; plat=$${plat#sdk/}; \
cmake --install "build/$$plat" --prefix "dist/eve-sdk/$$plat"; \
echo "Installed target SDK -> dist/eve-sdk/$$plat"
example: run/$(PLATFORM)-debug
# make: build/.build-docker
# $(DOCKER) /bin/bash -c "cmake -H./src -B./src/build && cmake --build src/build --parallel 8"
+15
View File
@@ -0,0 +1,15 @@
@PACKAGE_INIT@
set(EVENGINE_VERSION "@EVENGINE_SDK_VERSION@")
set(EVENGINE_TARGET_PLATFORM "@EVENGINE_TARGET_PLATFORM@")
get_filename_component(EVENGINE_ROOT "${CMAKE_CURRENT_LIST_DIR}/.." ABSOLUTE)
set(EVENGINE_INCLUDE_DIR "${EVENGINE_ROOT}/include/eve")
set(EVENGINE_THIRD_PARTY_INCLUDE_DIR "${EVENGINE_ROOT}/include")
set(EVENGINE_BIN_DIR "${EVENGINE_ROOT}/bin")
set(EVENGINE_LIB_DIR "${EVENGINE_ROOT}/lib")
set(EVENGINE_PLATFORM_DIR "${EVENGINE_ROOT}/platform")
include("${CMAKE_CURRENT_LIST_DIR}/EVEnginePlugin.cmake")
check_required_components(EVEngine)
+47
View File
@@ -0,0 +1,47 @@
# Helpers for building native plugins against a target-platform EVEngine SDK.
# Plugins are SHARED libraries loaded by eve.plugins.load; they do not link libeve.
function(add_eve_plugin _eve_plugin_name)
cmake_parse_arguments(_EVE_P "" "" "SOURCES;INCLUDE_DIRS;LIBRARIES" ${ARGN})
if(NOT _EVE_P_SOURCES)
message(FATAL_ERROR "add_eve_plugin(${_eve_plugin_name}): SOURCES required")
endif()
if(NOT EVENGINE_ROOT)
message(FATAL_ERROR "add_eve_plugin: EVENGINE_ROOT unset (call find_package(EVEngine) first)")
endif()
add_library(${_eve_plugin_name} SHARED ${_EVE_P_SOURCES})
target_compile_features(${_eve_plugin_name} PUBLIC cxx_std_20)
target_compile_definitions(${_eve_plugin_name} PRIVATE EVENGINE_PLUGIN=1)
target_include_directories(${_eve_plugin_name} PRIVATE
"${EVENGINE_INCLUDE_DIR}"
"${EVENGINE_THIRD_PARTY_INCLUDE_DIR}"
${_EVE_P_INCLUDE_DIRS}
)
if(WIN32)
# Link against the import library generated from eve.exe exports.
if(EXISTS "${EVENGINE_LIB_DIR}/eve.lib")
target_link_libraries(${_eve_plugin_name} PRIVATE "${EVENGINE_LIB_DIR}/eve.lib")
elseif(EXISTS "${EVENGINE_LIB_DIR}/eve.dll.a")
target_link_libraries(${_eve_plugin_name} PRIVATE "${EVENGINE_LIB_DIR}/eve.dll.a")
else()
message(WARNING "add_eve_plugin: eve import library not found under ${EVENGINE_LIB_DIR}")
endif()
elseif(APPLE)
# Resolve ModuleManager etc. from the host at load time.
target_link_options(${_eve_plugin_name} PRIVATE
"LINKER:-undefined,dynamic_lookup"
)
endif()
# Linux / Android: leave host symbols undefined; resolved when eve dlopens the plugin.
if(_EVE_P_LIBRARIES)
target_link_libraries(${_eve_plugin_name} PRIVATE ${_EVE_P_LIBRARIES})
endif()
set_target_properties(${_eve_plugin_name} PROPERTIES
PREFIX ""
OUTPUT_NAME "${_eve_plugin_name}"
)
endfunction()
+176
View File
@@ -0,0 +1,176 @@
# EVEngine SDK install rules — one independent SDK per BUILD_PLATFORM (target publish platform).
# Usage:
# cmake --install build/<plat>[-debug] --prefix dist/eve-sdk/<plat>[-debug]
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
set(EVENGINE_SDK_VERSION
"${EVENGINE_MAJOR_VERSION}.${EVENGINE_MINOR_VERSION}.${EVENGINE_PATCH_VERSION}")
# ---- Host binary ----
if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
install(TARGETS ${EVENGINE_NATIVE_TARGET}
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION lib
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
install(TARGETS ${EVENGINE_NATIVE_TARGET}
BUNDLE DESTINATION bin
RUNTIME DESTINATION bin
)
else()
install(TARGETS ${EVENGINE_NATIVE_TARGET}
RUNTIME DESTINATION bin
# Windows import library for plugins linking against eve.exe exports
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
endif()
# ---- Public headers (engine common + module façades) ----
install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/engine/common/"
DESTINATION include/eve/common
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
)
install(FILES "${CMAKE_BINARY_DIR}/src/engine/common/config.h"
DESTINATION include/eve/common
)
set(_eve_module_dirs
audio data event filesystem graphics image joystick keyboard mouse
model3d network particles plugins sound timer touch ui window
)
foreach(_eve_mod IN LISTS _eve_module_dirs)
if(EXISTS "${CMAKE_SOURCE_DIR}/src/modules/${_eve_mod}")
install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/modules/${_eve_mod}/"
DESTINATION include/eve/${_eve_mod}
FILES_MATCHING
PATTERN "*.h"
PATTERN "*.hpp"
PATTERN "sdl" EXCLUDE
PATTERN "vulkan" EXCLUDE
PATTERN "physfs" EXCLUDE
PATTERN "openal" EXCLUDE
PATTERN "imgui" EXCLUDE
PATTERN "cppfs" EXCLUDE
PATTERN "include_shim" EXCLUDE
)
endif()
endforeach()
# ---- Target-platform packaging template only ----
set(_eve_plat_src "${CMAKE_SOURCE_DIR}/platform/${BUILD_PLATFORM}")
if(EXISTS "${_eve_plat_src}")
if(BUILD_PLATFORM STREQUAL "android")
install(DIRECTORY "${_eve_plat_src}/"
DESTINATION platform
PATTERN "apk/app/build" EXCLUDE
PATTERN "apk/.gradle" EXCLUDE
PATTERN "apk/build" EXCLUDE
PATTERN "apk/local.properties" EXCLUDE
PATTERN "apk/app/src/main/jniLibs" EXCLUDE
PATTERN "apk/app/src/main/assets/game/*" EXCLUDE
PATTERN "CMakeCache.txt" EXCLUDE
PATTERN "*.o" EXCLUDE
PATTERN "*.a" EXCLUDE
)
# Keep assets/game placeholder
install(FILES "${CMAKE_SOURCE_DIR}/platform/android/apk/app/src/main/assets/game/.gitkeep"
DESTINATION platform/apk/app/src/main/assets/game
OPTIONAL
)
else()
install(DIRECTORY "${_eve_plat_src}/"
DESTINATION platform
PATTERN "CMakeCache.txt" EXCLUDE
PATTERN "*.o" EXCLUDE
PATTERN "*.a" EXCLUDE
)
endif()
endif()
# ---- Android runtime shared libs needed to assemble an APK ----
if(BUILD_PLATFORM STREQUAL "android")
set(_eve_tp_lib "${CMAKE_SOURCE_DIR}/build/third-party-binary/${BUILD_PLATFORM}")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(_eve_tp_lib "${CMAKE_SOURCE_DIR}/build/third-party-binary/${BUILD_PLATFORM}-debug")
endif()
if(EXISTS "${_eve_tp_lib}/lib/libSDL2.so")
install(FILES "${_eve_tp_lib}/lib/libSDL2.so" DESTINATION lib)
endif()
if(EXISTS "${_eve_tp_lib}/lib/libhidapi.so")
install(FILES "${_eve_tp_lib}/lib/libhidapi.so" DESTINATION lib)
endif()
# libc++_shared.so from NDK (best-effort; packaging docs note NDK fallback)
if(DEFINED ANDROID_NDK AND EXISTS "${ANDROID_NDK}")
file(GLOB _eve_cxx_shared
"${ANDROID_NDK}/toolchains/llvm/prebuilt/*/sysroot/usr/lib/aarch64-linux-android/libc++_shared.so")
if(_eve_cxx_shared)
list(GET _eve_cxx_shared 0 _eve_cxx_one)
install(FILES "${_eve_cxx_one}" DESTINATION lib)
endif()
endif()
endif()
# ---- Selected third-party headers needed to compile plugins (SSQ bindings) ----
set(_eve_tp_inc "${CMAKE_SOURCE_DIR}/build/third-party-binary/${BUILD_PLATFORM}")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(_eve_tp_inc "${CMAKE_SOURCE_DIR}/build/third-party-binary/${BUILD_PLATFORM}-debug")
endif()
if(EXISTS "${_eve_tp_inc}/include")
foreach(_eve_hdr_dir IN ITEMS simplesquirrel squirrel SQLiteCpp)
if(EXISTS "${_eve_tp_inc}/include/${_eve_hdr_dir}")
install(DIRECTORY "${_eve_tp_inc}/include/${_eve_hdr_dir}"
DESTINATION include
)
endif()
endforeach()
# Flat squirrel headers sometimes live at include/*.h
file(GLOB _eve_sq_headers
"${_eve_tp_inc}/include/squirrel.h"
"${_eve_tp_inc}/include/sqstdio.h"
"${_eve_tp_inc}/include/sqstdblob.h"
"${_eve_tp_inc}/include/sqstdmath.h"
"${_eve_tp_inc}/include/sqstdsystem.h"
"${_eve_tp_inc}/include/sqstdstring.h"
"${_eve_tp_inc}/include/sqconfig.h"
)
if(_eve_sq_headers)
install(FILES ${_eve_sq_headers} DESTINATION include)
endif()
endif()
# ---- Marker files ----
install(CODE "
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/share/eve/VERSION\" \"${EVENGINE_SDK_VERSION}\\n\")
file(WRITE \"\${CMAKE_INSTALL_PREFIX}/share/eve/TARGET_PLATFORM\" \"${BUILD_PLATFORM}\\n\")
")
# ---- CMake package config (find_package(EVEngine)) ----
set(EVENGINE_TARGET_PLATFORM "${BUILD_PLATFORM}")
configure_package_config_file(
"${CMAKE_SOURCE_DIR}/cmake/EVEngineConfig.cmake.in"
"${CMAKE_BINARY_DIR}/EVEngineConfig.cmake"
INSTALL_DESTINATION cmake
)
write_basic_package_version_file(
"${CMAKE_BINARY_DIR}/EVEngineConfigVersion.cmake"
VERSION "${EVENGINE_SDK_VERSION}"
COMPATIBILITY SameMajorVersion
)
install(FILES
"${CMAKE_BINARY_DIR}/EVEngineConfig.cmake"
"${CMAKE_BINARY_DIR}/EVEngineConfigVersion.cmake"
"${CMAKE_SOURCE_DIR}/cmake/EVEnginePlugin.cmake"
DESTINATION cmake
)
install(FILES "${CMAKE_SOURCE_DIR}/cmake/sdk/README.md"
DESTINATION share/eve
OPTIONAL
)
+47
View File
@@ -0,0 +1,47 @@
# EVEngine target-platform SDK
Each install prefix is an **independent SDK for one publish target**
(`win32` / `linux` / `macosx` / `android` / `ios`).
It is used on a **dev machine** to create a game that ships **to that target**.
It is not meant to be “installed and run” on a phone or as a universal multi-platform kit.
## Layout
- `bin/` — host (`eve` / `eve.exe` / iOS `.app`) when applicable
- `lib/` — Android `libmain.so` + packaging `.so`s; Windows `eve.lib` for plugins
- `include/eve/` — public headers for native plugins
- `cmake/` — `find_package(EVEngine)` + `add_eve_plugin()`
- `platform/` — **this target only** packaging template
- `share/eve/TARGET_PLATFORM` — must match the directory name
## Install (from engine source tree)
```bash
cmake --install build/macosx-debug --prefix dist/eve-sdk/macosx-debug
cmake --install build/android-debug --prefix dist/eve-sdk/android-debug
```
Or: `make sdk/macosx-debug` / `make sdk/android-debug`.
Do **not** install multiple platforms into the same prefix.
## Native plugin
```cmake
find_package(EVEngine REQUIRED PATHS /path/to/eve-sdk/macosx-debug)
add_eve_plugin(myplugin SOURCES myplugin.cpp)
```
Export `eve_plugin_init` (see `examples/native-plugin`). From Squirrel:
```squirrel
local plugins = eve.Plugins();
plugins.load("./myplugin.dylib"); // or .so / .dll
```
## Android packaging sketch
1. Copy SDK `lib/libmain.so`, `libSDL2.so`, `libc++_shared.so` into the template `jniLibs/<abi>/`.
2. Copy game scripts into `assets/game/`.
3. Build the Gradle project under `platform/apk/`.
+24 -2
View File
@@ -147,7 +147,7 @@ function eve::update(dt) {
| 组件 | 字段(草案) | 说明 |
|------|----------------|------|
| `Transform2D` | `x,y,rot,sx,sy` | 2D 变换;可后续拆 Parent 链接 |
| `Sprite` | texture、quad、color、layer、visible | 主 Renderable |
| `Sprite` | texture、shader、quad、color、layer、visible | 主 Renderable;`shader=null` 用默认管线 |
| `Camera2D`(P1) | 位置、zoom、清屏色 | 缺省一台绑定主窗口 |
| `CanvasTarget`(P1) | 离屏目标 | 声明画到哪 |
@@ -164,7 +164,29 @@ function eve::update(dt) {
| `present` / `setViewportSize` / `clear` | P0 | 帧与窗口;由 RenderSystem 或 boot 调用 |
| Batcher + 纹理 upload | P0 | Sprite 绘制后端 |
| 图元 tessellate | P2 | 调试形状、编辑器 gizmo |
| 自定义 Shader | P1 | 先进内部,再考虑组件字段 |
| 自定义 Shader | P1 | `Graphics::newShader` / `newShaderFromSpv*`;`Sprite.shader` |
### 3.4 自定义 Shader 约定(2D)
```cpp
auto *sh = gfx->newShader(R"(#version 450
layout(location=0) in vec4 fragColor;
layout(location=1) in vec2 fragUV;
layout(location=0) out vec4 outColor;
layout(binding=0) uniform sampler2D MainTex;
layout(push_constant) uniform Externals { float data[32]; } u;
void main() { outColor = texture(MainTex, fragUV) * fragColor * u.data[0]; }
)");
sh->declareFloat("factor");
sh->sendFloat("factor", 0.5f);
entity->sprite()->shader = sh; // 声明式
// 或 gfx->setShader(sh); // 即时模式逃生舱
```
- 顶点输入固定为 TexturedVertex(pos / color / uv);省略 vert 时用内置 `textured.vert`
- `MainTex`:`set=0, binding=0`
- Uniform:先 `declare*` 再 `send*`,按声明顺序写入 push-constant `data[]`(最多 32 floats)
- GLSL 编译依赖本机 `glslc`;无 glslc 时用预编译 SPIR-V(`newShaderFromSpv` / `newShaderFromSpvFile`)
## 4. VKBuilder 映射表
+4 -4
View File
@@ -69,8 +69,8 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。
- [ ] 纹理 / Quad 作为 Sprite 资源(image 解码 → GPU Texture)
- [ ] Vulkan 后端基础管线(VKBuilder Present / Pipeline / Texture)
- [ ] `clear` / `present`、与 SDL Window Surface 对接
- [ ] 离屏 Canvas(组件或内部目标,P1)
- [ ] 用户自定义 Shader(P1,先进内部)
- [x] 离屏 Canvas(组件或内部目标,P1)
- [x] 用户自定义 Shader(P1:`Shader` 资源 + `Sprite.shader` + `Graphics::newShader` / SPIR-V)
**高级能力(B,依赖 A,见设计文档第 8 节):**
@@ -79,7 +79,7 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。
- [ ] 地图类接口:TileLayer + tilemap
- [ ] 摄像机:2D 光照与法线贴图
- [ ] 低像素优化:小图旋转等
- [ ] 简单 3D model 支持
- [x] 简单 3D model 支持(`Model3D` 加载 + `Graphics::newMeshFromAssimp` / `RenderSystem3D`)
### 5. 数据抽象 — `data`
@@ -99,7 +99,7 @@ Love2D 风格即时模式仅作 C++/DevTools 内部逃生舱。
- [x] 立体声播放 — `audio`(OpenAL)
- [ ] 视频资源 — `video`
- [ ] 字体资源 — `fonts`
- [ ] 3D 模型资源 — `3dmodel`
- [x] 3D 模型资源 — `3dmodel`(`model3d` / `eve.Model3D`,medialoader Assimp → `ModelData`)
- [ ] 动画资源载入(与 `animation` 配合)
- [ ] 统一 cache、引用计数与热重载标记
+16
View File
@@ -118,6 +118,7 @@
| data | `src/modules/data/DataModule.h` | 可用 | 已展开 | **33/35 已覆盖**(2 部分) | `test/data.cpp`;22 个 `data.*` 用例 |
| image | `src/modules/image/Image.h` | 可用 | 已展开 | **见 §12** | `test/image.cpp`(10 用例) |
| sound | `src/modules/sound/Sound.h` | 可用 | 已展开 | **见 §13** | `test/sound.cpp`(7 用例) |
| model3d / 3dmodel | `src/modules/model3d/Model3D.h` | 可用 | 已展开 | **见 §13b** | `test/model3d.cpp`(5 用例);medialoader Assimp |
| audio | `src/modules/audio/Audio.h` | 可用 | 已展开 | **见 §14** | `test/audio.cpp`(9 用例);需 OpenAL |
| plugins | — | 未实现 | N/A | - | |
| system | — | 未实现 | N/A | - | |
@@ -320,6 +321,20 @@
程序化 demo 音效已迁至可选 `demo` 模块(`test/demo.cpp`)。
## 13b. API 明细:`model3d`(资源名 `3dmodel`)
用例:`test/model3d.cpp`(9)。CPU 解码 + 可选 GPU 渲染冒烟;上传见 `Graphics::newMeshFromAssimp` / `RenderSystem3D`。
| API | 头文件 | 状态 | 用例 | 缺口 |
|-----|--------|------|------|------|
| `Model3D::newModelData(Data*, hintExt)` | Model3D.h | 已覆盖 | `model3d.newModelData.objFromMemory`, `model3d.newModelData.emptyThrows`, `model3d.newModelData.rock1Fbx` | FileData 扩展名自动 hint 未单测 |
| `Model3D::newModelDataFromFile(path)` | Model3D.h | 已覆盖 | `model3d.newModelDataFromFile.missingThrows`, `model3d.newModelDataFromFile.sofaObjMtl` | |
| 渲染冒烟(ModelData → Mesh → RenderSystem3D) | — | 已覆盖 | `model3d.render.rock1Fbx`, `model3d.render.sofaObjMtl` | rock1 用 `Textures/Rock1_Diffuse.png` |
| `Model3D::getName` | Model3D.h | 已覆盖 | `model3d.Model3D.getName` | |
| `ModelData::getMeshCount` / `getVertexCount` / `getFaceCount` / `empty` / `getMesh` / `getScene` | ModelData.h | 已覆盖 | `model3d.newModelData.objFromMemory` | |
| `ModelData::getMaterialCount` / `hasNormals` / `hasTexCoords` | ModelData.h | 部分 | 间接经 obj 加载 | 非法 index 仅测了 getVertexCount |
| `ModelData` 非法 mesh index | ModelData.h | 已覆盖 | `model3d.newModelData.invalidMeshIndexThrows` | |
## 14. API 明细:`audio`
用例:`test/audio.cpp`(9)。无 OpenAL 时新用例 N/A skip。
@@ -364,6 +379,7 @@
|------|------|
| 2026-08-05 | 初稿:规范 + 全模块总表 + `data`/`filesystem` API 展开与现状 |
| 2026-08-05 | 样板轮落地:`test/data.cpp` +13 用例;新建 `test/filesystem_cpp.cpp`(8 用例);更新 §5–§7 进度;回归 `data`+`filesystem`+`FilesystemTest` 33/33 PASS |
| 2026-08-06 | 新增 `model3d`(`3dmodel`):medialoader Assimp → `ModelData`;`test/model3d.cpp` 5 用例 PASS |
| 2026-08-05 | Round 2:event/mouse/touch/window/image/sound/audio/network/timer C++ 补测;keyboard/joystick 标 N/A;`EVTouch` 链入 unit_test;回归过滤 69 PASS;放宽 `audio.source.seekTell`;更新 §5 与 §9–§16 |
| 2026-08-05 | 修复:`File::read`/`open("r")` PhysFS 模式;Mouse WarpMouse 窗口句柄;Audio `stopAll` 野指针;HttpRequest verifySsl 继承(HTTPS 仍 stub `tls`);补强相关断言 |
| 2026-08-06 | Window API 轮收尾:`Window.h` 范围 B 纯虚齐全;`test/window_cpp.cpp` 14 用例;`^window\..*$` 14/14 PASS;更新 §5 / §11 |
+8
View File
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.21)
project(eve_hello_plugin LANGUAGES CXX)
# Point at a target-platform SDK, e.g.:
# cmake -B build -DEVEngine_DIR=/path/to/dist/eve-sdk/macosx-debug/cmake
find_package(EVEngine REQUIRED)
add_eve_plugin(hello_plugin SOURCES hello_plugin.cpp)
+17
View File
@@ -0,0 +1,17 @@
# Native plugin example (no engine source required)
Build against an installed **target-platform** SDK:
```bash
cmake -B build -DEVEngine_DIR=$PWD/../../dist/eve-sdk/macosx-debug/cmake
cmake --build build
```
Then from a game directory (with the matching `eve` host):
```squirrel
local plugins = eve.Plugins();
plugins.load("./hello_plugin.dylib"); // .so on Linux/Android, .dll on Windows
local hi = eve.HelloPlugin();
print(hi.greet() + "\n");
```
+37
View File
@@ -0,0 +1,37 @@
#include "common/Export.h"
#include "common/Module.h"
#include <simplesquirrel/simplesquirrel.hpp>
#include <string>
// Minimal sample plugin: registers a Squirrel-visible module "HelloPlugin".
namespace eve::hello {
class HelloPlugin : public Module {
public:
Module_REG(HelloPlugin);
HelloPlugin() = default;
~HelloPlugin() override = default;
std::string greet() const { return "hello from native plugin"; }
};
Module_IMPL(HelloPlugin, new HelloPlugin());
void HelloPlugin::expose(ssq::Table& table) {
auto cls = table.addClass(name, HelloPlugin::create, false);
expose(cls);
}
void HelloPlugin::expose(ssq::Class& cls) {
cls.addFunc("getName", &HelloPlugin::getName);
cls.addFunc("greet", &HelloPlugin::greet);
}
} // namespace eve::hello
EVE_PLUGIN_EXPORT int eve_plugin_init(void) {
// Module_IMPL already registered HelloPlugin via static ModuleRegister.
return 0;
}
+18
View File
@@ -0,0 +1,18 @@
// Quick plugin load smoke (no window — may still init SDL via modules).
// Prefer running under example/ with a one-off script if needed.
function file_exists(path) {
try { file(path, "r").close(); return true; } catch (e) { return false; }
}
print("plugins smoke\n");
local plugins = eve.Plugins();
local path = "build/hello_plugin.dylib";
try {
plugins.load(path);
local hi = eve.HelloPlugin();
print(hi.greet() + "\n");
print("OK\n");
} catch (e) {
print("FAIL: " + e + "\n");
}
+13
View File
@@ -0,0 +1,13 @@
# android target SDK notes
This tree (under the android SDK's `platform/`) is the APK packaging template.
On a **dev machine** (not on-device):
1. Copy SDK `lib/libmain.so`, `libSDL2.so`, `libc++_shared.so` (and `libhidapi.so` if present)
into `apk/app/src/main/jniLibs/<abi>/`.
2. Copy your game into `apk/app/src/main/assets/game/`.
3. Build with Gradle (`./gradlew assembleDebug` / `assembleRelease`).
Native plugins (`.so`) for the same ABI go into `jniLibs` and are loaded from script
with `eve.Plugins().load(...)`.
+12
View File
@@ -0,0 +1,12 @@
# ios target SDK notes
This tree is the iOS packaging template inside `dist/eve-sdk/ios[-debug]`.
On a **macOS dev machine** with Xcode:
1. Use the prebuilt `bin/eve.app` (or rebuild packaging around the SDK host) as the app shell.
2. Place game assets under the app's `game/` resources (see `game-shell/`).
3. Ensure MoltenVK is embedded as in the engine build.
4. Sign and archive for device / App Store.
Native plugins are typically embeddable `.dylib` / frameworks loaded via `eve.Plugins().load(...)`.
+9 -1
View File
@@ -1,3 +1,11 @@
在linux下,我们构建一个全静态链接的库,对于发布则可以选择动态链接或裁剪后自己编译纯静态链接版本。
# linux target SDK notes
This directory is the packaging template shipped inside `dist/eve-sdk/linux[-debug]`.
Publish a Linux game by shipping:
1. `bin/eve` from the SDK
2. Your game folder (`config.nut`, `main.nut`, assets)
3. Optional native plugins (`.so`) loaded via `eve.Plugins().load(...)`
Prefer a fully static host when possible; plugins remain shared objects.
+11
View File
@@ -0,0 +1,11 @@
# macosx target SDK notes
This directory is the packaging template shipped inside `dist/eve-sdk/macosx[-debug]`.
Publish a macOS game by shipping:
1. `bin/eve` from the SDK
2. Your game folder (`config.nut`, `main.nut`, assets)
3. Optional native plugins (`.dylib`) loaded via `eve.Plugins().load(...)`
Requires a Vulkan-capable environment (MoltenVK) on the player machine, same as the engine host.
+11
View File
@@ -0,0 +1,11 @@
# win32 target SDK notes
This directory is the packaging template shipped inside `dist/eve-sdk/win32[-debug]`.
Publish a Windows game by shipping:
1. `bin/eve.exe` from the SDK
2. Your game folder (`config.nut`, `main.nut`, assets)
3. Optional native plugins (`.dll`) next to the game; load via `eve.Plugins().load(...)`
Develop on a Windows machine (or cross-build plugins matching the MSVC ABI of the SDK).
+24 -5
View File
@@ -9,14 +9,14 @@ set(EVENGINE_IOS_DEPLOYMENT_TARGET "13.0" CACHE STRING "Minimum iOS/iPadOS deplo
if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
# SDLActivity loads libmain.so and calls SDL_main (via SDL_main.h macro on main).
add_library(main SHARED main.cpp)
set(EVENGINE_NATIVE_TARGET main)
set(EVENGINE_NATIVE_TARGET main CACHE INTERNAL "Host target for SDK install")
elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
# UIKit SDL entry: MACOSX_BUNDLE .app with SDL_main.
add_executable(eve MACOSX_BUNDLE main.cpp)
set(EVENGINE_NATIVE_TARGET eve)
set(EVENGINE_NATIVE_TARGET eve CACHE INTERNAL "Host target for SDK install")
else()
add_executable(eve main.cpp)
set(EVENGINE_NATIVE_TARGET eve)
set(EVENGINE_NATIVE_TARGET eve CACHE INTERNAL "Host target for SDK install")
endif()
if(WIN32)
@@ -112,9 +112,9 @@ else()
endif()
if(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android") OR CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVTouch EVCmdLine EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles)
set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVTouch EVCmdLine EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVModel3D EVPlugins)
else()
set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVTouch EVCmdLine EVDevTools EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles)
set(EVELIBS EVWindow EVMouse EVKeyboard EVJoystick EVEvent EVTimer EVTouch EVCmdLine EVDevTools EVScripts EVCommon EVGraphics EVImage EVFileSystem EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVModel3D EVPlugins)
endif()
if(EVENGINE_BUILD_DEMO)
@@ -209,3 +209,22 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS)
COMMENT "Embed and sign MoltenVK.framework into EVEngine.app"
)
endif()
# Plugins resolve ModuleManager / ModuleRegister symbols from the host process.
target_compile_definitions(${EVENGINE_NATIVE_TARGET} PRIVATE EVENGINE_BUILDING_HOST=1)
if(WIN32)
set_target_properties(${EVENGINE_NATIVE_TARGET} PROPERTIES
ENABLE_EXPORTS ON
WINDOWS_EXPORT_ALL_SYMBOLS ON
)
elseif(APPLE AND NOT (CMAKE_SYSTEM_NAME STREQUAL "iOS" OR EVENGINE_IOS))
target_link_options(${EVENGINE_NATIVE_TARGET} PRIVATE "LINKER:-export_dynamic")
elseif(UNIX AND NOT ANDROID AND NOT (CMAKE_SYSTEM_NAME STREQUAL "Android"))
target_link_options(${EVENGINE_NATIVE_TARGET} PRIVATE "LINKER:--export-dynamic")
elseif(ANDROID OR (CMAKE_SYSTEM_NAME STREQUAL "Android"))
# libmain.so: keep default-visible EVENGINE_API symbols for dlopened plugins.
set_target_properties(${EVENGINE_NATIVE_TARGET} PROPERTIES
CXX_VISIBILITY_PRESET default
VISIBILITY_INLINES_HIDDEN OFF
)
endif()
+4 -2
View File
@@ -1,6 +1,8 @@
#pragma once
#include "common/Export.h"
#include <exception>
#include <string>
@@ -8,7 +10,7 @@ namespace eve
{
class Exception : public std::exception
class EVENGINE_API Exception : public std::exception
{
public:
Exception(const char *fmt, ...);
@@ -28,4 +30,4 @@ private:
};
}
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
// Host (eve / libmain) exports; plugins import the same symbols from the process.
#if defined(EVENGINE_PLUGIN)
# if defined(_WIN32)
# define EVENGINE_API __declspec(dllimport)
# else
# define EVENGINE_API
# endif
#elif defined(_WIN32)
# define EVENGINE_API __declspec(dllexport)
#else
# if defined(__GNUC__) || defined(__clang__)
# define EVENGINE_API __attribute__((visibility("default")))
# else
# define EVENGINE_API
# endif
#endif
// C ABI entry required by eve.plugins.load (return 0 on success).
#if defined(_WIN32)
# define EVE_PLUGIN_EXPORT extern "C" __declspec(dllexport)
#else
# define EVE_PLUGIN_EXPORT extern "C" __attribute__((visibility("default")))
#endif
+26
View File
@@ -33,12 +33,38 @@ void ModuleManager::register_module(const char* name, creator_t c, exposer_t e)
}
void ModuleManager::expose(ssq::VM& vm) {
inst().active_vm_ = &vm;
auto table = vm.addTable("eve");
for (auto& D : inst().registered_modules) {
D.second.exposer(table);
D.second.exposed = true;
}
}
void ModuleManager::set_vm(ssq::VM* vm) { inst().active_vm_ = vm; }
ssq::VM* ModuleManager::vm() { return inst().active_vm_; }
int ModuleManager::expose_pending() {
ssq::VM* v = inst().active_vm_;
if (!v)
return -1;
int count = 0;
try {
ssq::Table table(v->find("eve"));
for (auto& D : inst().registered_modules) {
if (D.second.exposed || !D.second.exposer)
continue;
D.second.exposer(table);
D.second.exposed = true;
++count;
}
} catch (...) {
return -1;
}
return count;
}
ModuleRegister::ModuleRegister(const char* name,
ModuleManager::creator_t c, ModuleManager::exposer_t e) {
ModuleManager::register_module(name, c, e);
+11 -3
View File
@@ -1,4 +1,6 @@
#pragma once
#include "common/Export.h"
#include <cstdint>
#include <string>
#include <unordered_map>
@@ -36,13 +38,13 @@ class Class;
namespace eve {
class Module {
class EVENGINE_API Module {
public:
virtual ~Module() {}
virtual std::string getName() const = 0;
};
class ModuleManager {
class EVENGINE_API ModuleManager {
public:
typedef Module* (*creator_t)();
typedef void (*exposer_t)(ssq::Table&);
@@ -54,6 +56,10 @@ public:
static void register_module(const char* name, creator_t c, exposer_t e);
static void expose(ssq::VM& vm);
// Expose modules registered after the initial expose() (e.g. plugins).
static int expose_pending();
static void set_vm(ssq::VM* vm);
static ssq::VM* vm();
template <typename T>
static T* getInstance(const char* name) {
@@ -72,12 +78,14 @@ protected:
creator_t creator = nullptr;
exposer_t exposer = nullptr;
Module* instance = nullptr;
bool exposed = false;
};
std::unordered_map<std::string, ModuleInfo> registered_modules;
ssq::VM* active_vm_ = nullptr;
};
struct ModuleRegister {
struct EVENGINE_API ModuleRegister {
ModuleRegister(const char* name, ModuleManager::creator_t c, ModuleManager::exposer_t e);
};
+2
View File
@@ -48,6 +48,8 @@ target_link_libraries(EVUI PUBLIC eve_imgui)
target_include_directories(EVUI PUBLIC ${CMAKE_SOURCE_DIR}/third-party/imgui)
create_module(EVParticles particles)
create_module(EVModel3D model3d)
create_module(EVPlugins plugins)
if(EVENGINE_BUILD_DEMO)
create_module(EVDemo demo)
endif()
+2 -1
View File
@@ -45,8 +45,9 @@
6. 管理系统 - system
负责系统版本、时间、状态等的查询和设置
7. 资源管理系统 - image sound video fonts 3dmodel animation
7. 资源管理系统 - image sound video fonts 3dmodel(`model3d`/`Model3D`) animation
负责加载图形、声音、视频、字体、3d模型、动画等不同格式数据
3dmodel:`Model3D::newModelData` / `newModelDataFromFile` → `ModelData`(medialoader Assimp)
8. 网络系统 - network
封装网络通讯的各种基础操作
+28 -1
View File
@@ -26,6 +26,20 @@ void Graphics::expose(ssq::Table &table) {
table.addClass<Texture>("Texture", std::function<Texture *()>([]() -> Texture * { return nullptr; }),
true);
auto shader = table.addClass<Shader>(
"Shader", std::function<Shader *()>([]() -> Shader * { return nullptr; }), true);
shader.addFunc("declareFloat", &Shader::declareFloat);
shader.addFunc("declareVec2", &Shader::declareVec2);
shader.addFunc("declareVec3", &Shader::declareVec3);
shader.addFunc("declareVec4", &Shader::declareVec4);
shader.addFunc("declareMatrix", &Shader::declareMatrix);
shader.addFunc("sendFloat", &Shader::sendFloat);
shader.addFunc("sendVec2", &Shader::sendVec2);
shader.addFunc("sendVec3", &Shader::sendVec3);
shader.addFunc("sendVec4", &Shader::sendVec4);
shader.addFunc("hasUniform", &Shader::hasUniform);
shader.addFunc("getUniformIndex", &Shader::getUniformIndex);
auto cam = table.addClass<Camera3D>(
"Camera3D", std::function<Camera3D *()>([]() { return Camera3D::createCamera(); }), true);
cam.addFunc("setEye", &Camera3D::setEye);
@@ -43,6 +57,7 @@ void Graphics::expose(ssq::Table &table) {
ent.addFunc("setScale", &Renderable3D::setScale);
ent.addFunc("setMesh", &Renderable3D::setMesh);
ent.addFunc("setTexture", &Renderable3D::setTexture);
ent.addFunc("setShader", &Renderable3D::setShader);
ent.addFunc("setTint", &Renderable3D::setTint);
ent.addFunc("setVisible", &Renderable3D::setVisible);
ent.addFunc("setCamera", &Renderable3D::setCamera);
@@ -56,11 +71,23 @@ void Graphics::expose(ssq::Class &cls) {
cls.addFunc("setBackgroundColor", &Graphics::setBackgroundColorRGBA);
cls.addFunc("drawSolidRect", &Graphics::drawSolidRectRGBA);
cls.addFunc("newMeshSphere", &Graphics::newMeshSphere);
cls.addFunc("newShader",
static_cast<Shader *(Graphics::*)(const std::string &)>(&Graphics::newShader));
cls.addFunc("newMeshShader",
static_cast<Shader *(Graphics::*)(const std::string &)>(&Graphics::newMeshShader));
cls.addFunc("newShaderFromSpvFile",
static_cast<Shader *(Graphics::*)(const std::string &)>(&Graphics::newShaderFromSpvFile));
cls.addFunc("setShader", static_cast<void (Graphics::*)(Shader *)>(&Graphics::setShader));
cls.addFunc("getShader", &Graphics::getShader);
cls.addFunc("render3D", &Graphics::render3D);
cls.addFunc("setDirectionalLight", &Graphics::setDirectionalLight);
}
void Graphics::reset() {}
void Graphics::reset() { currentShader = nullptr; }
void Graphics::setShader(Shader *shader) { currentShader = shader; }
void Graphics::setShader() { currentShader = nullptr; }
void Graphics::clearScreen() {
clear(std::nullopt, std::nullopt, std::nullopt);
+46 -2
View File
@@ -68,10 +68,15 @@ public:
/** Load file via Filesystem + Image decode, then upload (RGBA8). Throws on failure. */
virtual Texture *newTextureFromFile(const std::string &filename) = 0;
/** Draw a textured quad (full UV 0..1). texture may be null → solid. */
/** Draw a textured quad (full UV 0..1). texture may be null → solid.
* Uses currentShader when set (or per-call override via drawTexturedRectShader). */
virtual void drawTexturedRect(Texture *texture, float x, float y, float w, float h,
const Color &color) = 0;
/** Draw with an explicit Shader (nullptr = default textured pipeline). */
virtual void drawTexturedRectShader(Texture *texture, Shader *shader, float x, float y, float w,
float h, const Color &color) = 0;
/** Upload triangulated mesh from Assimp (pos/normal/uv + indices). Owned by Graphics. */
virtual Mesh *newMeshFromAssimp(const ::aiMesh &mesh) = 0;
@@ -99,9 +104,16 @@ virtual void begin3DFrame() = 0;
/** Draw one mesh with model matrix. Requires begin3DFrame() (or an open swapchain pass). */
virtual void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) = 0;
/** Draw mesh with an explicit Mesh3D Shader (nullptr = default lit pipeline). */
virtual void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint,
Shader *shader) = 0;
/** Directional light for subsequent drawMesh calls (world-space direction toward surface). */
virtual void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) = 0;
/** Camera eye used by mesh shaders that need view/rim (stored in Mesh3DUBO). */
virtual void setMesh3DCameraPos(const glm::vec3 &eye) = 0;
/** True after begin3DFrame until present completes. */
bool consumeFrameHad3D() {
bool v = frameHad3D;
@@ -155,7 +167,38 @@ virtual void begin3DFrame() = 0;
void setShader(Shader *shader);
void setShader();
Shader *getShader() const;
Shader *getShader() const { return currentShader; }
/**
* Create a custom 2D shader from SPIR-V words (vert + frag).
* Owned by Graphics. Vertex stage may be empty → uses the default textured vertex shader.
*/
virtual Shader *newShaderFromSpv(const std::vector<uint32_t> &vertSpv,
const std::vector<uint32_t> &fragSpv) = 0;
/** Load SPIR-V from files via Filesystem (empty vertPath → default textured vert). */
virtual Shader *newShaderFromSpvFile(const std::string &vertPath, const std::string &fragPath) = 0;
Shader *newShaderFromSpvFile(const std::string &fragPath) {
return newShaderFromSpvFile(std::string(), fragPath);
}
/**
* Compile GLSL source with glslc (must be on PATH). Empty vertGlsl → default textured vert.
* Throws if compilation fails.
*/
virtual Shader *newShader(const std::string &vertGlsl, const std::string &fragGlsl) = 0;
Shader *newShader(const std::string &fragGlsl) { return newShader(std::string(), fragGlsl); }
/**
* Create a Mesh3D custom shader (MeshVertex + Frame UBO + albedo).
* Empty vert → default mesh3d.vert. Owned by Graphics.
*/
virtual Shader *newMeshShaderFromSpv(const std::vector<uint32_t> &vertSpv,
const std::vector<uint32_t> &fragSpv) = 0;
virtual Shader *newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) = 0;
Shader *newMeshShader(const std::string &fragGlsl) {
return newMeshShader(std::string(), fragGlsl);
}
/** Create an offscreen render target (sampleable). Owned by Graphics. */
virtual Canvas *newCanvas(int width, int height) = 0;
@@ -287,6 +330,7 @@ protected:
bool graphicsActive = true;
PresentOverlayFn presentOverlayFn_ = nullptr;
void *presentOverlayUser_ = nullptr;
Shader *currentShader = nullptr;
};
} // namespace eve::graphics
+4 -1
View File
@@ -75,6 +75,7 @@ void RenderSystem::render(Graphics &gfx) {
Color color;
int layer;
Texture *texture;
Shader *shader;
Canvas *canvas;
ViewCam cam;
};
@@ -99,6 +100,7 @@ void RenderSystem::render(Graphics &gfx) {
item.color = Color(sp->r, sp->g, sp->b, sp->a);
item.layer = sp->layer;
item.texture = sp->texture;
item.shader = sp->shader;
item.canvas = sp->canvas;
item.cam = fromEntity(camEnt);
items.push_back(item);
@@ -110,6 +112,7 @@ void RenderSystem::render(Graphics &gfx) {
if (aOff != bOff) return aOff && !bOff;
if (a.canvas != b.canvas) return a.canvas < b.canvas;
if (a.layer != b.layer) return a.layer < b.layer;
if (a.shader != b.shader) return a.shader < b.shader;
return a.texture < b.texture;
});
@@ -134,7 +137,7 @@ void RenderSystem::render(Graphics &gfx) {
float sx, sy, sw, sh;
applyCamera(it.x, it.y, it.w, it.h, it.cam, viewW, viewH, sx, sy, sw, sh);
if (it.texture)
gfx.drawTexturedRect(it.texture, sx, sy, sw, sh, it.color);
gfx.drawTexturedRectShader(it.texture, it.shader, sx, sy, sw, sh, it.color);
else
gfx.drawSolidRect(sx, sy, sw, sh, it.color);
}
+2
View File
@@ -1,6 +1,7 @@
#pragma once
#include "common/ECS.h"
#include "graphics/Shader.h"
#include "graphics/Texture.h"
#include "zeroerr/assert.h"
#include <cstdint>
@@ -60,6 +61,7 @@ public:
int layer = 0;
bool visible = true;
Texture *texture = nullptr;
Shader *shader = nullptr; // nullptr → default textured / solid pipeline
Canvas *canvas = nullptr; // nullptr → screen
Camera2D *camera = nullptr; // nullptr → default active camera for canvas
};
+4 -1
View File
@@ -88,6 +88,8 @@ void Renderable3D::setMesh(Mesh *mesh) { meshRenderer()->mesh = mesh; }
void Renderable3D::setTexture(Texture *texture) { meshRenderer()->texture = texture; }
void Renderable3D::setShader(Shader *shader) { meshRenderer()->shader = shader; }
void Renderable3D::setTint(float r, float g, float b, float a) {
auto mr = meshRenderer();
mr->r = r;
@@ -137,10 +139,11 @@ void RenderSystem3D::render(Graphics &gfx) {
const float fovRad = cd->fovYDeg * 0.017453292519943295f;
const glm::mat4 projM = glm::perspectiveRH_ZO(fovRad, aspect, cd->nearZ, cd->farZ);
gfx.setMesh3DViewProj(projM * viewM);
gfx.setMesh3DCameraPos(eye);
const glm::mat4 model = modelFromTransform(*xf);
const Color tint(mr->r, mr->g, mr->b, mr->a);
gfx.drawMesh(mr->mesh, model, mr->texture, tint);
gfx.drawMeshShader(mr->mesh, model, mr->texture, tint, mr->shader);
}
}
+3
View File
@@ -2,6 +2,7 @@
#include "common/ECS.h"
#include "graphics/Mesh.h"
#include "graphics/Shader.h"
#include "graphics/Texture.h"
#include "zeroerr/assert.h"
@@ -56,6 +57,7 @@ public:
struct MeshRenderer {
Mesh *mesh = nullptr;
Texture *texture = nullptr;
Shader *shader = nullptr; // nullptr → default mesh3d lit pipeline
float r = 1, g = 1, b = 1, a = 1;
bool visible = true;
Camera3D *camera = nullptr;
@@ -71,6 +73,7 @@ public:
void setScale(float sx, float sy, float sz);
void setMesh(Mesh *mesh);
void setTexture(Texture *texture);
void setShader(Shader *shader);
void setTint(float r, float g, float b, float a = 1.f);
void setVisible(bool visible);
void setCamera(Camera3D *camera);
+103
View File
@@ -0,0 +1,103 @@
#include "graphics/Shader.h"
#include "common/Exception.h"
#include <cstring>
namespace eve::graphics {
Shader::Shader() = default;
Shader::~Shader() = default;
int Shader::declareUniform(const std::string &name, int floatCount) {
if (name.empty()) throw Exception("Shader::declare: empty uniform name");
if (floatCount <= 0) throw Exception("Shader::declare: invalid float count");
if (auto *existing = findUniform(name)) {
if (existing->floatCount != floatCount)
throw Exception("Shader::declare: '%s' already declared with different size", name.c_str());
return existing->index;
}
if (usedFloats_ + floatCount > kMaxFloats)
throw Exception("Shader::declare: push-constant block full (max %d floats)", kMaxFloats);
Uniform u;
u.index = usedFloats_;
u.floatCount = floatCount;
uniforms_[name] = u;
usedFloats_ += floatCount;
return u.index;
}
int Shader::declareFloat(const std::string &name) { return declareUniform(name, 1); }
int Shader::declareVec2(const std::string &name) { return declareUniform(name, 2); }
int Shader::declareVec3(const std::string &name) { return declareUniform(name, 3); }
int Shader::declareVec4(const std::string &name) { return declareUniform(name, 4); }
int Shader::declareMatrix(const std::string &name) { return declareUniform(name, 16); }
Shader::Uniform *Shader::findUniform(const std::string &name) {
auto it = uniforms_.find(name);
return it == uniforms_.end() ? nullptr : &it->second;
}
const Shader::Uniform *Shader::findUniform(const std::string &name) const {
auto it = uniforms_.find(name);
return it == uniforms_.end() ? nullptr : &it->second;
}
bool Shader::hasUniform(const std::string &name) const { return findUniform(name) != nullptr; }
int Shader::getUniformIndex(const std::string &name) const {
auto *u = findUniform(name);
if (!u) throw Exception("Shader: unknown uniform '%s'", name.c_str());
return u->index;
}
int Shader::getUniformFloatCount(const std::string &name) const {
auto *u = findUniform(name);
if (!u) throw Exception("Shader: unknown uniform '%s'", name.c_str());
return u->floatCount;
}
int Shader::sendToVar(const std::string &name, const void *data, size_t size) {
auto *u = findUniform(name);
if (!u) throw Exception("Shader::send: unknown uniform '%s' (declare it first)", name.c_str());
if (!data) throw Exception("Shader::send: null data for '%s'", name.c_str());
const size_t expected = size_t(u->floatCount) * sizeof(float);
if (size != expected)
throw Exception("Shader::send: '%s' expects %zu bytes, got %zu", name.c_str(), expected, size);
std::memcpy(floats_.data() + u->index, data, size);
return int(size);
}
int Shader::getFromVar(const std::string &name, void *data, size_t size) const {
auto *u = findUniform(name);
if (!u) throw Exception("Shader::get: unknown uniform '%s'", name.c_str());
if (!data) throw Exception("Shader::get: null data for '%s'", name.c_str());
const size_t expected = size_t(u->floatCount) * sizeof(float);
if (size != expected)
throw Exception("Shader::get: '%s' expects %zu bytes, got %zu", name.c_str(), expected, size);
std::memcpy(data, floats_.data() + u->index, size);
return int(size);
}
void Shader::sendFloat(const std::string &name, float x) { sendToVar(name, &x, sizeof(x)); }
void Shader::sendVec2(const std::string &name, float x, float y) {
const float v[2] = {x, y};
sendToVar(name, v, sizeof(v));
}
void Shader::sendVec3(const std::string &name, float x, float y, float z) {
const float v[3] = {x, y, z};
sendToVar(name, v, sizeof(v));
}
void Shader::sendVec4(const std::string &name, float x, float y, float z, float w) {
const float v[4] = {x, y, z, w};
sendToVar(name, v, sizeof(v));
}
void Shader::sendMatrix(const std::string &name, const glm::mat4 &m) {
sendToVar(name, &m[0][0], sizeof(float) * 16);
}
} // namespace eve::graphics
+82 -24
View File
@@ -1,45 +1,103 @@
#pragma once
#include <string>
#include <array>
#include <cstddef>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
#include <stddef.h>
#include <glm/mat4x4.hpp>
namespace eve::graphics
{
namespace eve::graphics {
class Graphics;
// A GLSL shader
class Shader
{
/**
* Custom GPU program.
*
* **2D sprites** (`newShader` / `newShaderFromSpv`):
* Vertex = TexturedVertex; MainTex @ set0 binding0; optional push-constant data[32].
*
* **3D meshes** (`newMeshShader` / `newMeshShaderFromSpv`):
* Vertex = MeshVertex; Frame UBO @ binding0 + albedo @ binding1 (same as mesh3d);
* optional push-constant data[32] for style knobs.
*/
class Shader {
public:
enum ShaderType
{
eCompute,
static constexpr int kMaxFloats = 32;
static constexpr uint32_t kPushConstantBytes = uint32_t(kMaxFloats * sizeof(float));
enum class Kind { eSprite2D, eMesh3D };
enum ShaderType {
eCompute,
eVertex,
eFragment,
eGeometry,
eTessCtrl,
eTessEval
};
};
// Pointer to currently active Shader.
static Shader *current;
Shader();
~Shader();
// Pointer to the default Shader.
static Shader *defaultShaders[3];
Shader(const Shader &) = delete;
Shader &operator=(const Shader &) = delete;
Shader();
virtual ~Shader();
Kind getKind() const { return kind_; }
void setKind(Kind k) { kind_ = k; }
int sendToVar(const std::string &name, const void *data, size_t size);
int getFromVar(const std::string &name, void *data, size_t size);
/** Reserve sequential float slots in the push-constant block. Returns start index. */
int declareFloat(const std::string &name);
int declareVec2(const std::string &name);
int declareVec3(const std::string &name);
int declareVec4(const std::string &name);
int declareMatrix(const std::string &name);
void sendFloat(const std::string &name, float x);
void sendVec2(const std::string &name, float x, float y);
void sendVec3(const std::string &name, float x, float y, float z);
void sendVec4(const std::string &name, float x, float y, float z, float w);
void sendMatrix(const std::string &name, const glm::mat4 &m);
/** Low-level blob write into a declared uniform (size must match declaration). */
int sendToVar(const std::string &name, const void *data, size_t size);
int getFromVar(const std::string &name, void *data, size_t size) const;
}; // Shader
bool hasUniform(const std::string &name) const;
int getUniformIndex(const std::string &name) const;
int getUniformFloatCount(const std::string &name) const;
} // eve::graphics
const float *pushConstantData() const { return floats_.data(); }
uint32_t pushConstantSize() const { return uint32_t(usedFloats_ * sizeof(float)); }
int usedFloats() const { return usedFloats_; }
const std::vector<uint32_t> &vertexSpirv() const { return vertSpv_; }
const std::vector<uint32_t> &fragmentSpirv() const { return fragSpv_; }
void setSpirv(std::vector<uint32_t> vert, std::vector<uint32_t> frag) {
vertSpv_ = std::move(vert);
fragSpv_ = std::move(frag);
}
/** Backend-private GPU object (vulkan::GpuShader*). */
void *gpuHandle = nullptr;
private:
struct Uniform {
int index = 0;
int floatCount = 0;
};
int declareUniform(const std::string &name, int floatCount);
Uniform *findUniform(const std::string &name);
const Uniform *findUniform(const std::string &name) const;
Kind kind_ = Kind::eSprite2D;
std::vector<uint32_t> vertSpv_;
std::vector<uint32_t> fragSpv_;
std::map<std::string, Uniform> uniforms_;
std::array<float, kMaxFloats> floats_{};
int usedFloats_ = 0;
};
} // namespace eve::graphics
@@ -0,0 +1,16 @@
#version 450
// Custom 2D fragment shader sample: tint RGB by push-constant factor.
// Uniform layout (declare before send):
// declareFloat("factor") → u.data[0]
layout(location = 0) in vec4 fragColor;
layout(location = 1) in vec2 fragUV;
layout(location = 0) out vec4 outColor;
layout(binding = 0) uniform sampler2D MainTex;
layout(push_constant) uniform Externals {
float data[32];
} u;
void main() {
vec4 c = texture(MainTex, fragUV) * fragColor;
float f = u.data[0];
outColor = vec4(c.rgb * f, c.a);
}
Binary file not shown.
@@ -0,0 +1,46 @@
static const uint32_t custom2d_frag_spv[] = {
0x07230203, 0x00010000, 0x000d000b, 0x00000033, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0008000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000011, 0x00000015, 0x00000026,
0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47,
0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576,
0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669,
0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00030005, 0x00000009, 0x00000063, 0x00040005,
0x0000000d, 0x6e69614d, 0x00786554, 0x00040005, 0x00000011, 0x67617266, 0x00005655, 0x00050005,
0x00000015, 0x67617266, 0x6f6c6f43, 0x00000072, 0x00030005, 0x00000019, 0x00000066, 0x00050005,
0x0000001d, 0x65747845, 0x6c616e72, 0x00000073, 0x00050006, 0x0000001d, 0x00000000, 0x61746164,
0x00000000, 0x00030005, 0x0000001f, 0x00000075, 0x00050005, 0x00000026, 0x4374756f, 0x726f6c6f,
0x00000000, 0x00040047, 0x0000000d, 0x00000021, 0x00000000, 0x00040047, 0x0000000d, 0x00000022,
0x00000000, 0x00040047, 0x00000011, 0x0000001e, 0x00000001, 0x00040047, 0x00000015, 0x0000001e,
0x00000000, 0x00040047, 0x0000001c, 0x00000006, 0x00000004, 0x00030047, 0x0000001d, 0x00000002,
0x00050048, 0x0000001d, 0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x00000026, 0x0000001e,
0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006,
0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000007,
0x00000007, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, 0x00040020, 0x0000000c, 0x00000000,
0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040017, 0x0000000f, 0x00000006,
0x00000002, 0x00040020, 0x00000010, 0x00000001, 0x0000000f, 0x0004003b, 0x00000010, 0x00000011,
0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000007, 0x0004003b, 0x00000014, 0x00000015,
0x00000001, 0x00040020, 0x00000018, 0x00000007, 0x00000006, 0x00040015, 0x0000001a, 0x00000020,
0x00000000, 0x0004002b, 0x0000001a, 0x0000001b, 0x00000020, 0x0004001c, 0x0000001c, 0x00000006,
0x0000001b, 0x0003001e, 0x0000001d, 0x0000001c, 0x00040020, 0x0000001e, 0x00000009, 0x0000001d,
0x0004003b, 0x0000001e, 0x0000001f, 0x00000009, 0x00040015, 0x00000020, 0x00000020, 0x00000001,
0x0004002b, 0x00000020, 0x00000021, 0x00000000, 0x00040020, 0x00000022, 0x00000009, 0x00000006,
0x00040020, 0x00000025, 0x00000003, 0x00000007, 0x0004003b, 0x00000025, 0x00000026, 0x00000003,
0x00040017, 0x00000027, 0x00000006, 0x00000003, 0x0004002b, 0x0000001a, 0x0000002c, 0x00000003,
0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b,
0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000018, 0x00000019, 0x00000007, 0x0004003d,
0x0000000b, 0x0000000e, 0x0000000d, 0x0004003d, 0x0000000f, 0x00000012, 0x00000011, 0x00050057,
0x00000007, 0x00000013, 0x0000000e, 0x00000012, 0x0004003d, 0x00000007, 0x00000016, 0x00000015,
0x00050085, 0x00000007, 0x00000017, 0x00000013, 0x00000016, 0x0003003e, 0x00000009, 0x00000017,
0x00060041, 0x00000022, 0x00000023, 0x0000001f, 0x00000021, 0x00000021, 0x0004003d, 0x00000006,
0x00000024, 0x00000023, 0x0003003e, 0x00000019, 0x00000024, 0x0004003d, 0x00000007, 0x00000028,
0x00000009, 0x0008004f, 0x00000027, 0x00000029, 0x00000028, 0x00000028, 0x00000000, 0x00000001,
0x00000002, 0x0004003d, 0x00000006, 0x0000002a, 0x00000019, 0x0005008e, 0x00000027, 0x0000002b,
0x00000029, 0x0000002a, 0x00050041, 0x00000018, 0x0000002d, 0x00000009, 0x0000002c, 0x0004003d,
0x00000006, 0x0000002e, 0x0000002d, 0x00050051, 0x00000006, 0x0000002f, 0x0000002b, 0x00000000,
0x00050051, 0x00000006, 0x00000030, 0x0000002b, 0x00000001, 0x00050051, 0x00000006, 0x00000031,
0x0000002b, 0x00000002, 0x00070050, 0x00000007, 0x00000032, 0x0000002f, 0x00000030, 0x00000031,
0x0000002e, 0x0003003e, 0x00000026, 0x00000032, 0x000100fd, 0x00010038,
};
static const size_t custom2d_frag_spv_count = sizeof(custom2d_frag_spv)/sizeof(custom2d_frag_spv[0]);
+4 -2
View File
@@ -15,7 +15,9 @@ void main() {
vec3 N = normalize(vNormal);
vec3 L = normalize(vLightDir);
float ndotl = max(dot(N, L), 0.0);
float ambient = 0.15;
vec3 lit = base.rgb * (ambient + ndotl * vLightColor);
// Soften just a little so silhouette isn't pure black, but keep contrast.
float diffuse = mix(ndotl, ndotl * 0.5 + 0.5, 0.25);
float ambient = 0.22;
vec3 lit = base.rgb * (ambient + diffuse * vLightColor);
outColor = vec4(lit, base.a);
}
Binary file not shown.
@@ -1,59 +1,62 @@
static const uint32_t mesh3d_frag_spv[] = {
0x07230203, 0x00010000, 0x000d000b, 0x00000042, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x07230203, 0x00010000, 0x0008000b, 0x0000004a, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x000b000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000011, 0x00000015, 0x0000001c,
0x00000020, 0x00000031, 0x00000038, 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002,
0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70, 0x5f656c79, 0x656e696c,
0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f, 0x6e695f45, 0x64756c63,
0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00040005,
0x00000009, 0x65736162, 0x00000000, 0x00040005, 0x0000000d, 0x65626c61, 0x00006f64, 0x00030005,
0x00000011, 0x00565576, 0x00040005, 0x00000015, 0x6e695476, 0x00000074, 0x00030005, 0x0000001a,
0x0000004e, 0x00040005, 0x0000001c, 0x726f4e76, 0x006c616d, 0x00030005, 0x0000001f, 0x0000004c,
0x00050005, 0x00000020, 0x67694c76, 0x69447468, 0x00000072, 0x00040005, 0x00000024, 0x746f646e,
0x0000006c, 0x00040005, 0x0000002a, 0x69626d61, 0x00746e65, 0x00030005, 0x0000002c, 0x0074696c,
0x00050005, 0x00000031, 0x67694c76, 0x6f437468, 0x00726f6c, 0x00050005, 0x00000038, 0x4374756f,
0x726f6c6f, 0x00000000, 0x00040047, 0x0000000d, 0x00000021, 0x00000001, 0x00040047, 0x0000000d,
0x00000022, 0x00000000, 0x00040047, 0x00000011, 0x0000001e, 0x00000001, 0x00040047, 0x00000015,
0x0000001e, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000, 0x00040047, 0x00000020,
0x0000001e, 0x00000003, 0x00040047, 0x00000031, 0x0000001e, 0x00000004, 0x00040047, 0x00000038,
0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016,
0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008,
0x00000007, 0x00000007, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000, 0x00000000,
0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, 0x00040020, 0x0000000c,
0x00000000, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040017, 0x0000000f,
0x00000006, 0x00000002, 0x00040020, 0x00000010, 0x00000001, 0x0000000f, 0x0004003b, 0x00000010,
0x00000011, 0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000007, 0x0004003b, 0x00000014,
0x00000015, 0x00000001, 0x00040017, 0x00000018, 0x00000006, 0x00000003, 0x00040020, 0x00000019,
0x00000007, 0x00000018, 0x00040020, 0x0000001b, 0x00000001, 0x00000018, 0x0004003b, 0x0000001b,
0x0000001c, 0x00000001, 0x0004003b, 0x0000001b, 0x00000020, 0x00000001, 0x00040020, 0x00000023,
0x00000007, 0x00000006, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x0004002b, 0x00000006,
0x0000002b, 0x3e19999a, 0x0004003b, 0x0000001b, 0x00000031, 0x00000001, 0x00040020, 0x00000037,
0x00000003, 0x00000007, 0x0004003b, 0x00000037, 0x00000038, 0x00000003, 0x00040015, 0x0000003a,
0x00000020, 0x00000000, 0x0004002b, 0x0000003a, 0x0000003b, 0x00000003, 0x00050036, 0x00000002,
0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000008, 0x00000009,
0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000007, 0x0004003b, 0x00000019, 0x0000001f,
0x00000007, 0x0004003b, 0x00000023, 0x00000024, 0x00000007, 0x0004003b, 0x00000023, 0x0000002a,
0x00000007, 0x0004003b, 0x00000019, 0x0000002c, 0x00000007, 0x0004003d, 0x0000000b, 0x0000000e,
0x0000000d, 0x0004003d, 0x0000000f, 0x00000012, 0x00000011, 0x00050057, 0x00000007, 0x00000013,
0x0000000e, 0x00000012, 0x0004003d, 0x00000007, 0x00000016, 0x00000015, 0x00050085, 0x00000007,
0x00000017, 0x00000013, 0x00000016, 0x0003003e, 0x00000009, 0x00000017, 0x0004003d, 0x00000018,
0x0000001d, 0x0000001c, 0x0006000c, 0x00000018, 0x0000001e, 0x00000001, 0x00000045, 0x0000001d,
0x0003003e, 0x0000001a, 0x0000001e, 0x0004003d, 0x00000018, 0x00000021, 0x00000020, 0x0006000c,
0x00000018, 0x00000022, 0x00000001, 0x00000045, 0x00000021, 0x0003003e, 0x0000001f, 0x00000022,
0x0004003d, 0x00000018, 0x00000025, 0x0000001a, 0x0004003d, 0x00000018, 0x00000026, 0x0000001f,
0x00050094, 0x00000006, 0x00000027, 0x00000025, 0x00000026, 0x0007000c, 0x00000006, 0x00000029,
0x00000001, 0x00000028, 0x00000027, 0x00000028, 0x0003003e, 0x00000024, 0x00000029, 0x0003003e,
0x0000002a, 0x0000002b, 0x0004003d, 0x00000007, 0x0000002d, 0x00000009, 0x0008004f, 0x00000018,
0x0000002e, 0x0000002d, 0x0000002d, 0x00000000, 0x00000001, 0x00000002, 0x0004003d, 0x00000006,
0x0000002f, 0x0000002a, 0x0004003d, 0x00000006, 0x00000030, 0x00000024, 0x0004003d, 0x00000018,
0x00000032, 0x00000031, 0x0005008e, 0x00000018, 0x00000033, 0x00000032, 0x00000030, 0x00060050,
0x00000018, 0x00000034, 0x0000002f, 0x0000002f, 0x0000002f, 0x00050081, 0x00000018, 0x00000035,
0x00000034, 0x00000033, 0x00050085, 0x00000018, 0x00000036, 0x0000002e, 0x00000035, 0x0003003e,
0x0000002c, 0x00000036, 0x0004003d, 0x00000018, 0x00000039, 0x0000002c, 0x00050041, 0x00000023,
0x0000003c, 0x00000009, 0x0000003b, 0x0004003d, 0x00000006, 0x0000003d, 0x0000003c, 0x00050051,
0x00000006, 0x0000003e, 0x00000039, 0x00000000, 0x00050051, 0x00000006, 0x0000003f, 0x00000039,
0x00000001, 0x00050051, 0x00000006, 0x00000040, 0x00000039, 0x00000002, 0x00070050, 0x00000007,
0x00000041, 0x0000003e, 0x0000003f, 0x00000040, 0x0000003d, 0x0003003e, 0x00000038, 0x00000041,
0x000100fd, 0x00010038
0x00000020, 0x00000039, 0x00000040, 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002,
0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00040005, 0x00000009, 0x65736162,
0x00000000, 0x00040005, 0x0000000d, 0x65626c61, 0x00006f64, 0x00030005, 0x00000011, 0x00565576,
0x00040005, 0x00000015, 0x6e695476, 0x00000074, 0x00030005, 0x0000001a, 0x0000004e, 0x00040005,
0x0000001c, 0x726f4e76, 0x006c616d, 0x00030005, 0x0000001f, 0x0000004c, 0x00050005, 0x00000020,
0x67694c76, 0x69447468, 0x00000072, 0x00040005, 0x00000024, 0x746f646e, 0x0000006c, 0x00040005,
0x0000002a, 0x66666964, 0x00657375, 0x00040005, 0x00000032, 0x69626d61, 0x00746e65, 0x00030005,
0x00000034, 0x0074696c, 0x00050005, 0x00000039, 0x67694c76, 0x6f437468, 0x00726f6c, 0x00050005,
0x00000040, 0x4374756f, 0x726f6c6f, 0x00000000, 0x00040047, 0x0000000d, 0x00000021, 0x00000001,
0x00040047, 0x0000000d, 0x00000022, 0x00000000, 0x00040047, 0x00000011, 0x0000001e, 0x00000001,
0x00040047, 0x00000015, 0x0000001e, 0x00000002, 0x00040047, 0x0000001c, 0x0000001e, 0x00000000,
0x00040047, 0x00000020, 0x0000001e, 0x00000003, 0x00040047, 0x00000039, 0x0000001e, 0x00000004,
0x00040047, 0x00000040, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003,
0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004,
0x00040020, 0x00000008, 0x00000007, 0x00000007, 0x00090019, 0x0000000a, 0x00000006, 0x00000001,
0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a,
0x00040020, 0x0000000c, 0x00000000, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000,
0x00040017, 0x0000000f, 0x00000006, 0x00000002, 0x00040020, 0x00000010, 0x00000001, 0x0000000f,
0x0004003b, 0x00000010, 0x00000011, 0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000007,
0x0004003b, 0x00000014, 0x00000015, 0x00000001, 0x00040017, 0x00000018, 0x00000006, 0x00000003,
0x00040020, 0x00000019, 0x00000007, 0x00000018, 0x00040020, 0x0000001b, 0x00000001, 0x00000018,
0x0004003b, 0x0000001b, 0x0000001c, 0x00000001, 0x0004003b, 0x0000001b, 0x00000020, 0x00000001,
0x00040020, 0x00000023, 0x00000007, 0x00000006, 0x0004002b, 0x00000006, 0x00000028, 0x00000000,
0x0004002b, 0x00000006, 0x0000002d, 0x3f000000, 0x0004002b, 0x00000006, 0x00000030, 0x3e800000,
0x0004002b, 0x00000006, 0x00000033, 0x3e6147ae, 0x0004003b, 0x0000001b, 0x00000039, 0x00000001,
0x00040020, 0x0000003f, 0x00000003, 0x00000007, 0x0004003b, 0x0000003f, 0x00000040, 0x00000003,
0x00040015, 0x00000042, 0x00000020, 0x00000000, 0x0004002b, 0x00000042, 0x00000043, 0x00000003,
0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b,
0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000007, 0x0004003b,
0x00000019, 0x0000001f, 0x00000007, 0x0004003b, 0x00000023, 0x00000024, 0x00000007, 0x0004003b,
0x00000023, 0x0000002a, 0x00000007, 0x0004003b, 0x00000023, 0x00000032, 0x00000007, 0x0004003b,
0x00000019, 0x00000034, 0x00000007, 0x0004003d, 0x0000000b, 0x0000000e, 0x0000000d, 0x0004003d,
0x0000000f, 0x00000012, 0x00000011, 0x00050057, 0x00000007, 0x00000013, 0x0000000e, 0x00000012,
0x0004003d, 0x00000007, 0x00000016, 0x00000015, 0x00050085, 0x00000007, 0x00000017, 0x00000013,
0x00000016, 0x0003003e, 0x00000009, 0x00000017, 0x0004003d, 0x00000018, 0x0000001d, 0x0000001c,
0x0006000c, 0x00000018, 0x0000001e, 0x00000001, 0x00000045, 0x0000001d, 0x0003003e, 0x0000001a,
0x0000001e, 0x0004003d, 0x00000018, 0x00000021, 0x00000020, 0x0006000c, 0x00000018, 0x00000022,
0x00000001, 0x00000045, 0x00000021, 0x0003003e, 0x0000001f, 0x00000022, 0x0004003d, 0x00000018,
0x00000025, 0x0000001a, 0x0004003d, 0x00000018, 0x00000026, 0x0000001f, 0x00050094, 0x00000006,
0x00000027, 0x00000025, 0x00000026, 0x0007000c, 0x00000006, 0x00000029, 0x00000001, 0x00000028,
0x00000027, 0x00000028, 0x0003003e, 0x00000024, 0x00000029, 0x0004003d, 0x00000006, 0x0000002b,
0x00000024, 0x0004003d, 0x00000006, 0x0000002c, 0x00000024, 0x00050085, 0x00000006, 0x0000002e,
0x0000002c, 0x0000002d, 0x00050081, 0x00000006, 0x0000002f, 0x0000002e, 0x0000002d, 0x0008000c,
0x00000006, 0x00000031, 0x00000001, 0x0000002e, 0x0000002b, 0x0000002f, 0x00000030, 0x0003003e,
0x0000002a, 0x00000031, 0x0003003e, 0x00000032, 0x00000033, 0x0004003d, 0x00000007, 0x00000035,
0x00000009, 0x0008004f, 0x00000018, 0x00000036, 0x00000035, 0x00000035, 0x00000000, 0x00000001,
0x00000002, 0x0004003d, 0x00000006, 0x00000037, 0x00000032, 0x0004003d, 0x00000006, 0x00000038,
0x0000002a, 0x0004003d, 0x00000018, 0x0000003a, 0x00000039, 0x0005008e, 0x00000018, 0x0000003b,
0x0000003a, 0x00000038, 0x00060050, 0x00000018, 0x0000003c, 0x00000037, 0x00000037, 0x00000037,
0x00050081, 0x00000018, 0x0000003d, 0x0000003c, 0x0000003b, 0x00050085, 0x00000018, 0x0000003e,
0x00000036, 0x0000003d, 0x0003003e, 0x00000034, 0x0000003e, 0x0004003d, 0x00000018, 0x00000041,
0x00000034, 0x00050041, 0x00000023, 0x00000044, 0x00000009, 0x00000043, 0x0004003d, 0x00000006,
0x00000045, 0x00000044, 0x00050051, 0x00000006, 0x00000046, 0x00000041, 0x00000000, 0x00050051,
0x00000006, 0x00000047, 0x00000041, 0x00000001, 0x00050051, 0x00000006, 0x00000048, 0x00000041,
0x00000002, 0x00070050, 0x00000007, 0x00000049, 0x00000046, 0x00000047, 0x00000048, 0x00000045,
0x0003003e, 0x00000040, 0x00000049, 0x000100fd, 0x00010038,
};
static const size_t mesh3d_frag_spv_count = 442;
static const size_t mesh3d_frag_spv_count = 469;
@@ -0,0 +1,55 @@
#version 450
// Stylized cel / toon fragment for Mesh3D (Rock demo).
// Uses same Frame UBO + albedo sampler as mesh3d.frag.
// Push constants (optional): data[0]=bands, data[1]=rimPower,
// data[2]=rimStrength, data[3]=posterizeSteps
layout(location = 0) in vec3 vNormal;
layout(location = 1) in vec2 vUV;
layout(location = 2) in vec4 vTint;
layout(location = 3) in vec3 vLightDir;
layout(location = 4) in vec3 vLightColor;
layout(location = 5) in vec3 vWorldPos;
layout(location = 6) in vec3 vCameraPos;
layout(set = 0, binding = 1) uniform sampler2D albedo;
layout(push_constant) uniform Externals {
float data[32];
} u;
layout(location = 0) out vec4 outColor;
void main() {
vec4 base = texture(albedo, vUV) * vTint;
vec3 N = normalize(vNormal);
vec3 L = normalize(vLightDir);
float ndotl = max(dot(N, L), 0.0);
float bands = max(u.data[0], 2.0);
float rimPower = max(u.data[1], 0.5);
float rimStrength = u.data[2];
float posterize = max(u.data[3], 2.0);
// Soft cel bands
float cel = floor(ndotl * bands) / max(bands - 1.0, 1.0);
// Warm shadow / cool mid / warm highlight ramp
vec3 shadowCol = vec3(0.18, 0.14, 0.28);
vec3 midCol = vec3(0.85, 0.72, 0.55);
vec3 hiCol = vec3(1.15, 1.05, 0.90);
vec3 ramp = mix(shadowCol, midCol, clamp(cel * 1.4, 0.0, 1.0));
ramp = mix(ramp, hiCol, smoothstep(0.65, 1.0, cel));
// Posterize albedo toward painted look
vec3 alb = base.rgb;
alb = floor(alb * posterize + 0.5) / posterize;
// Rim light (view-facing edges)
vec3 V = normalize(vCameraPos - vWorldPos);
float rim = pow(clamp(1.0 - max(dot(N, V), 0.0), 0.0, 1.0), rimPower);
vec3 rimCol = vec3(0.55, 0.85, 1.15) * rim * rimStrength;
float ambient = 0.18;
vec3 lit = alb * (ambient + ramp * vLightColor) + rimCol * alb;
outColor = vec4(lit, base.a);
}
Binary file not shown.
@@ -0,0 +1,36 @@
#version 450
// Mesh3D vertex for stylized shaders — same UBO as mesh3d.vert, plus world/camera for rim.
layout(location = 0) in vec3 inPos;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inUV;
layout(set = 0, binding = 0) uniform Frame {
mat4 mvp;
mat4 model;
vec4 lightDirIntensity;
vec4 lightColor;
vec4 tint;
vec4 cameraPos; // xyz
} ubo;
layout(location = 0) out vec3 vNormal;
layout(location = 1) out vec2 vUV;
layout(location = 2) out vec4 vTint;
layout(location = 3) out vec3 vLightDir;
layout(location = 4) out vec3 vLightColor;
layout(location = 5) out vec3 vWorldPos;
layout(location = 6) out vec3 vCameraPos;
void main() {
gl_Position = ubo.mvp * vec4(inPos, 1.0);
vec4 world = ubo.model * vec4(inPos, 1.0);
vWorldPos = world.xyz;
mat3 normalMat = mat3(ubo.model);
vNormal = normalize(normalMat * inNormal);
vUV = inUV;
vTint = ubo.tint;
vLightDir = normalize(ubo.lightDirIntensity.xyz);
vLightColor = ubo.lightColor.rgb;
vCameraPos = ubo.cameraPos.xyz;
}
Binary file not shown.
@@ -0,0 +1,139 @@
static const uint32_t mesh3d_toon_frag_spv[] = {
0x07230203, 0x00010000, 0x000d000b, 0x000000ab, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x000d000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000011, 0x00000015, 0x0000001c,
0x00000020, 0x0000007d, 0x0000007f, 0x00000097, 0x000000a2, 0x00030010, 0x00000004, 0x00000007,
0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70,
0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f,
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d,
0x00000000, 0x00040005, 0x00000009, 0x65736162, 0x00000000, 0x00040005, 0x0000000d, 0x65626c61,
0x00006f64, 0x00030005, 0x00000011, 0x00565576, 0x00040005, 0x00000015, 0x6e695476, 0x00000074,
0x00030005, 0x0000001a, 0x0000004e, 0x00040005, 0x0000001c, 0x726f4e76, 0x006c616d, 0x00030005,
0x0000001f, 0x0000004c, 0x00050005, 0x00000020, 0x67694c76, 0x69447468, 0x00000072, 0x00040005,
0x00000024, 0x746f646e, 0x0000006c, 0x00040005, 0x0000002a, 0x646e6162, 0x00000073, 0x00050005,
0x0000002e, 0x65747845, 0x6c616e72, 0x00000073, 0x00050006, 0x0000002e, 0x00000000, 0x61746164,
0x00000000, 0x00030005, 0x00000030, 0x00000075, 0x00050005, 0x00000038, 0x506d6972, 0x7265776f,
0x00000000, 0x00050005, 0x0000003e, 0x536d6972, 0x6e657274, 0x00687467, 0x00050005, 0x00000042,
0x74736f70, 0x7a697265, 0x00000065, 0x00030005, 0x00000047, 0x006c6563, 0x00050005, 0x00000051,
0x64616873, 0x6f43776f, 0x0000006c, 0x00040005, 0x00000056, 0x4364696d, 0x00006c6f, 0x00040005,
0x0000005b, 0x6f436968, 0x0000006c, 0x00040005, 0x00000060, 0x706d6172, 0x00000000, 0x00030005,
0x00000070, 0x00626c61, 0x00030005, 0x0000007c, 0x00000056, 0x00050005, 0x0000007d, 0x6d614376,
0x50617265, 0x0000736f, 0x00050005, 0x0000007f, 0x726f5776, 0x6f50646c, 0x00000073, 0x00030005,
0x00000083, 0x006d6972, 0x00040005, 0x0000008c, 0x436d6972, 0x00006c6f, 0x00040005, 0x00000092,
0x69626d61, 0x00746e65, 0x00030005, 0x00000093, 0x0074696c, 0x00050005, 0x00000097, 0x67694c76,
0x6f437468, 0x00726f6c, 0x00050005, 0x000000a2, 0x4374756f, 0x726f6c6f, 0x00000000, 0x00040047,
0x0000000d, 0x00000021, 0x00000001, 0x00040047, 0x0000000d, 0x00000022, 0x00000000, 0x00040047,
0x00000011, 0x0000001e, 0x00000001, 0x00040047, 0x00000015, 0x0000001e, 0x00000002, 0x00040047,
0x0000001c, 0x0000001e, 0x00000000, 0x00040047, 0x00000020, 0x0000001e, 0x00000003, 0x00040047,
0x0000002d, 0x00000006, 0x00000004, 0x00030047, 0x0000002e, 0x00000002, 0x00050048, 0x0000002e,
0x00000000, 0x00000023, 0x00000000, 0x00040047, 0x0000007d, 0x0000001e, 0x00000006, 0x00040047,
0x0000007f, 0x0000001e, 0x00000005, 0x00040047, 0x00000097, 0x0000001e, 0x00000004, 0x00040047,
0x000000a2, 0x0000001e, 0x00000000, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002,
0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040020,
0x00000008, 0x00000007, 0x00000007, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000,
0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x0003001b, 0x0000000b, 0x0000000a, 0x00040020,
0x0000000c, 0x00000000, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000000, 0x00040017,
0x0000000f, 0x00000006, 0x00000002, 0x00040020, 0x00000010, 0x00000001, 0x0000000f, 0x0004003b,
0x00000010, 0x00000011, 0x00000001, 0x00040020, 0x00000014, 0x00000001, 0x00000007, 0x0004003b,
0x00000014, 0x00000015, 0x00000001, 0x00040017, 0x00000018, 0x00000006, 0x00000003, 0x00040020,
0x00000019, 0x00000007, 0x00000018, 0x00040020, 0x0000001b, 0x00000001, 0x00000018, 0x0004003b,
0x0000001b, 0x0000001c, 0x00000001, 0x0004003b, 0x0000001b, 0x00000020, 0x00000001, 0x00040020,
0x00000023, 0x00000007, 0x00000006, 0x0004002b, 0x00000006, 0x00000028, 0x00000000, 0x00040015,
0x0000002b, 0x00000020, 0x00000000, 0x0004002b, 0x0000002b, 0x0000002c, 0x00000020, 0x0004001c,
0x0000002d, 0x00000006, 0x0000002c, 0x0003001e, 0x0000002e, 0x0000002d, 0x00040020, 0x0000002f,
0x00000009, 0x0000002e, 0x0004003b, 0x0000002f, 0x00000030, 0x00000009, 0x00040015, 0x00000031,
0x00000020, 0x00000001, 0x0004002b, 0x00000031, 0x00000032, 0x00000000, 0x00040020, 0x00000033,
0x00000009, 0x00000006, 0x0004002b, 0x00000006, 0x00000036, 0x40000000, 0x0004002b, 0x00000031,
0x00000039, 0x00000001, 0x0004002b, 0x00000006, 0x0000003c, 0x3f000000, 0x0004002b, 0x00000031,
0x0000003f, 0x00000002, 0x0004002b, 0x00000031, 0x00000043, 0x00000003, 0x0004002b, 0x00000006,
0x0000004d, 0x3f800000, 0x0004002b, 0x00000006, 0x00000052, 0x3e3851ec, 0x0004002b, 0x00000006,
0x00000053, 0x3e0f5c29, 0x0004002b, 0x00000006, 0x00000054, 0x3e8f5c29, 0x0006002c, 0x00000018,
0x00000055, 0x00000052, 0x00000053, 0x00000054, 0x0004002b, 0x00000006, 0x00000057, 0x3f59999a,
0x0004002b, 0x00000006, 0x00000058, 0x3f3851ec, 0x0004002b, 0x00000006, 0x00000059, 0x3f0ccccd,
0x0006002c, 0x00000018, 0x0000005a, 0x00000057, 0x00000058, 0x00000059, 0x0004002b, 0x00000006,
0x0000005c, 0x3f933333, 0x0004002b, 0x00000006, 0x0000005d, 0x3f866666, 0x0004002b, 0x00000006,
0x0000005e, 0x3f666666, 0x0006002c, 0x00000018, 0x0000005f, 0x0000005c, 0x0000005d, 0x0000005e,
0x0004002b, 0x00000006, 0x00000064, 0x3fb33333, 0x0004002b, 0x00000006, 0x0000006b, 0x3f266666,
0x0004003b, 0x0000001b, 0x0000007d, 0x00000001, 0x0004003b, 0x0000001b, 0x0000007f, 0x00000001,
0x0006002c, 0x00000018, 0x0000008d, 0x00000059, 0x00000057, 0x0000005c, 0x0004003b, 0x0000001b,
0x00000097, 0x00000001, 0x00040020, 0x000000a1, 0x00000003, 0x00000007, 0x0004003b, 0x000000a1,
0x000000a2, 0x00000003, 0x0004002b, 0x0000002b, 0x000000a4, 0x00000003, 0x00050036, 0x00000002,
0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000008, 0x00000009,
0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000007, 0x0004003b, 0x00000019, 0x0000001f,
0x00000007, 0x0004003b, 0x00000023, 0x00000024, 0x00000007, 0x0004003b, 0x00000023, 0x0000002a,
0x00000007, 0x0004003b, 0x00000023, 0x00000038, 0x00000007, 0x0004003b, 0x00000023, 0x0000003e,
0x00000007, 0x0004003b, 0x00000023, 0x00000042, 0x00000007, 0x0004003b, 0x00000023, 0x00000047,
0x00000007, 0x0004003b, 0x00000019, 0x00000051, 0x00000007, 0x0004003b, 0x00000019, 0x00000056,
0x00000007, 0x0004003b, 0x00000019, 0x0000005b, 0x00000007, 0x0004003b, 0x00000019, 0x00000060,
0x00000007, 0x0004003b, 0x00000019, 0x00000070, 0x00000007, 0x0004003b, 0x00000019, 0x0000007c,
0x00000007, 0x0004003b, 0x00000023, 0x00000083, 0x00000007, 0x0004003b, 0x00000019, 0x0000008c,
0x00000007, 0x0004003b, 0x00000023, 0x00000092, 0x00000007, 0x0004003b, 0x00000019, 0x00000093,
0x00000007, 0x0004003d, 0x0000000b, 0x0000000e, 0x0000000d, 0x0004003d, 0x0000000f, 0x00000012,
0x00000011, 0x00050057, 0x00000007, 0x00000013, 0x0000000e, 0x00000012, 0x0004003d, 0x00000007,
0x00000016, 0x00000015, 0x00050085, 0x00000007, 0x00000017, 0x00000013, 0x00000016, 0x0003003e,
0x00000009, 0x00000017, 0x0004003d, 0x00000018, 0x0000001d, 0x0000001c, 0x0006000c, 0x00000018,
0x0000001e, 0x00000001, 0x00000045, 0x0000001d, 0x0003003e, 0x0000001a, 0x0000001e, 0x0004003d,
0x00000018, 0x00000021, 0x00000020, 0x0006000c, 0x00000018, 0x00000022, 0x00000001, 0x00000045,
0x00000021, 0x0003003e, 0x0000001f, 0x00000022, 0x0004003d, 0x00000018, 0x00000025, 0x0000001a,
0x0004003d, 0x00000018, 0x00000026, 0x0000001f, 0x00050094, 0x00000006, 0x00000027, 0x00000025,
0x00000026, 0x0007000c, 0x00000006, 0x00000029, 0x00000001, 0x00000028, 0x00000027, 0x00000028,
0x0003003e, 0x00000024, 0x00000029, 0x00060041, 0x00000033, 0x00000034, 0x00000030, 0x00000032,
0x00000032, 0x0004003d, 0x00000006, 0x00000035, 0x00000034, 0x0007000c, 0x00000006, 0x00000037,
0x00000001, 0x00000028, 0x00000035, 0x00000036, 0x0003003e, 0x0000002a, 0x00000037, 0x00060041,
0x00000033, 0x0000003a, 0x00000030, 0x00000032, 0x00000039, 0x0004003d, 0x00000006, 0x0000003b,
0x0000003a, 0x0007000c, 0x00000006, 0x0000003d, 0x00000001, 0x00000028, 0x0000003b, 0x0000003c,
0x0003003e, 0x00000038, 0x0000003d, 0x00060041, 0x00000033, 0x00000040, 0x00000030, 0x00000032,
0x0000003f, 0x0004003d, 0x00000006, 0x00000041, 0x00000040, 0x0003003e, 0x0000003e, 0x00000041,
0x00060041, 0x00000033, 0x00000044, 0x00000030, 0x00000032, 0x00000043, 0x0004003d, 0x00000006,
0x00000045, 0x00000044, 0x0007000c, 0x00000006, 0x00000046, 0x00000001, 0x00000028, 0x00000045,
0x00000036, 0x0003003e, 0x00000042, 0x00000046, 0x0004003d, 0x00000006, 0x00000048, 0x00000024,
0x0004003d, 0x00000006, 0x00000049, 0x0000002a, 0x00050085, 0x00000006, 0x0000004a, 0x00000048,
0x00000049, 0x0006000c, 0x00000006, 0x0000004b, 0x00000001, 0x00000008, 0x0000004a, 0x0004003d,
0x00000006, 0x0000004c, 0x0000002a, 0x00050083, 0x00000006, 0x0000004e, 0x0000004c, 0x0000004d,
0x0007000c, 0x00000006, 0x0000004f, 0x00000001, 0x00000028, 0x0000004e, 0x0000004d, 0x00050088,
0x00000006, 0x00000050, 0x0000004b, 0x0000004f, 0x0003003e, 0x00000047, 0x00000050, 0x0003003e,
0x00000051, 0x00000055, 0x0003003e, 0x00000056, 0x0000005a, 0x0003003e, 0x0000005b, 0x0000005f,
0x0004003d, 0x00000018, 0x00000061, 0x00000051, 0x0004003d, 0x00000018, 0x00000062, 0x00000056,
0x0004003d, 0x00000006, 0x00000063, 0x00000047, 0x00050085, 0x00000006, 0x00000065, 0x00000063,
0x00000064, 0x0008000c, 0x00000006, 0x00000066, 0x00000001, 0x0000002b, 0x00000065, 0x00000028,
0x0000004d, 0x00060050, 0x00000018, 0x00000067, 0x00000066, 0x00000066, 0x00000066, 0x0008000c,
0x00000018, 0x00000068, 0x00000001, 0x0000002e, 0x00000061, 0x00000062, 0x00000067, 0x0003003e,
0x00000060, 0x00000068, 0x0004003d, 0x00000018, 0x00000069, 0x00000060, 0x0004003d, 0x00000018,
0x0000006a, 0x0000005b, 0x0004003d, 0x00000006, 0x0000006c, 0x00000047, 0x0008000c, 0x00000006,
0x0000006d, 0x00000001, 0x00000031, 0x0000006b, 0x0000004d, 0x0000006c, 0x00060050, 0x00000018,
0x0000006e, 0x0000006d, 0x0000006d, 0x0000006d, 0x0008000c, 0x00000018, 0x0000006f, 0x00000001,
0x0000002e, 0x00000069, 0x0000006a, 0x0000006e, 0x0003003e, 0x00000060, 0x0000006f, 0x0004003d,
0x00000007, 0x00000071, 0x00000009, 0x0008004f, 0x00000018, 0x00000072, 0x00000071, 0x00000071,
0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000070, 0x00000072, 0x0004003d, 0x00000018,
0x00000073, 0x00000070, 0x0004003d, 0x00000006, 0x00000074, 0x00000042, 0x0005008e, 0x00000018,
0x00000075, 0x00000073, 0x00000074, 0x00060050, 0x00000018, 0x00000076, 0x0000003c, 0x0000003c,
0x0000003c, 0x00050081, 0x00000018, 0x00000077, 0x00000075, 0x00000076, 0x0006000c, 0x00000018,
0x00000078, 0x00000001, 0x00000008, 0x00000077, 0x0004003d, 0x00000006, 0x00000079, 0x00000042,
0x00060050, 0x00000018, 0x0000007a, 0x00000079, 0x00000079, 0x00000079, 0x00050088, 0x00000018,
0x0000007b, 0x00000078, 0x0000007a, 0x0003003e, 0x00000070, 0x0000007b, 0x0004003d, 0x00000018,
0x0000007e, 0x0000007d, 0x0004003d, 0x00000018, 0x00000080, 0x0000007f, 0x00050083, 0x00000018,
0x00000081, 0x0000007e, 0x00000080, 0x0006000c, 0x00000018, 0x00000082, 0x00000001, 0x00000045,
0x00000081, 0x0003003e, 0x0000007c, 0x00000082, 0x0004003d, 0x00000018, 0x00000084, 0x0000001a,
0x0004003d, 0x00000018, 0x00000085, 0x0000007c, 0x00050094, 0x00000006, 0x00000086, 0x00000084,
0x00000085, 0x0007000c, 0x00000006, 0x00000087, 0x00000001, 0x00000028, 0x00000086, 0x00000028,
0x00050083, 0x00000006, 0x00000088, 0x0000004d, 0x00000087, 0x0008000c, 0x00000006, 0x00000089,
0x00000001, 0x0000002b, 0x00000088, 0x00000028, 0x0000004d, 0x0004003d, 0x00000006, 0x0000008a,
0x00000038, 0x0007000c, 0x00000006, 0x0000008b, 0x00000001, 0x0000001a, 0x00000089, 0x0000008a,
0x0003003e, 0x00000083, 0x0000008b, 0x0004003d, 0x00000006, 0x0000008e, 0x00000083, 0x0005008e,
0x00000018, 0x0000008f, 0x0000008d, 0x0000008e, 0x0004003d, 0x00000006, 0x00000090, 0x0000003e,
0x0005008e, 0x00000018, 0x00000091, 0x0000008f, 0x00000090, 0x0003003e, 0x0000008c, 0x00000091,
0x0003003e, 0x00000092, 0x00000052, 0x0004003d, 0x00000018, 0x00000094, 0x00000070, 0x0004003d,
0x00000006, 0x00000095, 0x00000092, 0x0004003d, 0x00000018, 0x00000096, 0x00000060, 0x0004003d,
0x00000018, 0x00000098, 0x00000097, 0x00050085, 0x00000018, 0x00000099, 0x00000096, 0x00000098,
0x00060050, 0x00000018, 0x0000009a, 0x00000095, 0x00000095, 0x00000095, 0x00050081, 0x00000018,
0x0000009b, 0x0000009a, 0x00000099, 0x00050085, 0x00000018, 0x0000009c, 0x00000094, 0x0000009b,
0x0004003d, 0x00000018, 0x0000009d, 0x0000008c, 0x0004003d, 0x00000018, 0x0000009e, 0x00000070,
0x00050085, 0x00000018, 0x0000009f, 0x0000009d, 0x0000009e, 0x00050081, 0x00000018, 0x000000a0,
0x0000009c, 0x0000009f, 0x0003003e, 0x00000093, 0x000000a0, 0x0004003d, 0x00000018, 0x000000a3,
0x00000093, 0x00050041, 0x00000023, 0x000000a5, 0x00000009, 0x000000a4, 0x0004003d, 0x00000006,
0x000000a6, 0x000000a5, 0x00050051, 0x00000006, 0x000000a7, 0x000000a3, 0x00000000, 0x00050051,
0x00000006, 0x000000a8, 0x000000a3, 0x00000001, 0x00050051, 0x00000006, 0x000000a9, 0x000000a3,
0x00000002, 0x00070050, 0x00000007, 0x000000aa, 0x000000a7, 0x000000a8, 0x000000a9, 0x000000a6,
0x0003003e, 0x000000a2, 0x000000aa, 0x000100fd, 0x00010038,
};
static const size_t mesh3d_toon_frag_spv_count = sizeof(mesh3d_toon_frag_spv)/sizeof(mesh3d_toon_frag_spv[0]);
@@ -0,0 +1,100 @@
static const uint32_t mesh3d_toon_vert_spv[] = {
0x07230203, 0x00010000, 0x000d000b, 0x0000005f, 0x00000000, 0x00020011, 0x00000001, 0x0006000b,
0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001,
0x0010000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000019, 0x0000002f,
0x0000003e, 0x00000040, 0x00000046, 0x00000048, 0x0000004a, 0x0000004f, 0x00000055, 0x0000005a,
0x00030003, 0x00000002, 0x000001c2, 0x000a0004, 0x475f4c47, 0x4c474f4f, 0x70635f45, 0x74735f70,
0x5f656c79, 0x656e696c, 0x7269645f, 0x69746365, 0x00006576, 0x00080004, 0x475f4c47, 0x4c474f4f,
0x6e695f45, 0x64756c63, 0x69645f65, 0x74636572, 0x00657669, 0x00040005, 0x00000004, 0x6e69616d,
0x00000000, 0x00060005, 0x0000000b, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, 0x00060006,
0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001,
0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67,
0x4470696c, 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75,
0x61747369, 0x0065636e, 0x00030005, 0x0000000d, 0x00000000, 0x00040005, 0x00000011, 0x6d617246,
0x00000065, 0x00040006, 0x00000011, 0x00000000, 0x0070766d, 0x00050006, 0x00000011, 0x00000001,
0x65646f6d, 0x0000006c, 0x00080006, 0x00000011, 0x00000002, 0x6867696c, 0x72694474, 0x65746e49,
0x7469736e, 0x00000079, 0x00060006, 0x00000011, 0x00000003, 0x6867696c, 0x6c6f4374, 0x0000726f,
0x00050006, 0x00000011, 0x00000004, 0x746e6974, 0x00000000, 0x00060006, 0x00000011, 0x00000005,
0x656d6163, 0x6f506172, 0x00000073, 0x00030005, 0x00000013, 0x006f6275, 0x00040005, 0x00000019,
0x6f506e69, 0x00000073, 0x00040005, 0x00000024, 0x6c726f77, 0x00000064, 0x00050005, 0x0000002f,
0x726f5776, 0x6f50646c, 0x00000073, 0x00050005, 0x00000034, 0x6d726f6e, 0x614d6c61, 0x00000074,
0x00040005, 0x0000003e, 0x726f4e76, 0x006c616d, 0x00050005, 0x00000040, 0x6f4e6e69, 0x6c616d72,
0x00000000, 0x00030005, 0x00000046, 0x00565576, 0x00040005, 0x00000048, 0x56556e69, 0x00000000,
0x00040005, 0x0000004a, 0x6e695476, 0x00000074, 0x00050005, 0x0000004f, 0x67694c76, 0x69447468,
0x00000072, 0x00050005, 0x00000055, 0x67694c76, 0x6f437468, 0x00726f6c, 0x00050005, 0x0000005a,
0x6d614376, 0x50617265, 0x0000736f, 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b,
0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001,
0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000000b, 0x00000003,
0x0000000b, 0x00000004, 0x00030047, 0x00000011, 0x00000002, 0x00040048, 0x00000011, 0x00000000,
0x00000005, 0x00050048, 0x00000011, 0x00000000, 0x00000007, 0x00000010, 0x00050048, 0x00000011,
0x00000000, 0x00000023, 0x00000000, 0x00040048, 0x00000011, 0x00000001, 0x00000005, 0x00050048,
0x00000011, 0x00000001, 0x00000007, 0x00000010, 0x00050048, 0x00000011, 0x00000001, 0x00000023,
0x00000040, 0x00050048, 0x00000011, 0x00000002, 0x00000023, 0x00000080, 0x00050048, 0x00000011,
0x00000003, 0x00000023, 0x00000090, 0x00050048, 0x00000011, 0x00000004, 0x00000023, 0x000000a0,
0x00050048, 0x00000011, 0x00000005, 0x00000023, 0x000000b0, 0x00040047, 0x00000013, 0x00000021,
0x00000000, 0x00040047, 0x00000013, 0x00000022, 0x00000000, 0x00040047, 0x00000019, 0x0000001e,
0x00000000, 0x00040047, 0x0000002f, 0x0000001e, 0x00000005, 0x00040047, 0x0000003e, 0x0000001e,
0x00000000, 0x00040047, 0x00000040, 0x0000001e, 0x00000001, 0x00040047, 0x00000046, 0x0000001e,
0x00000001, 0x00040047, 0x00000048, 0x0000001e, 0x00000002, 0x00040047, 0x0000004a, 0x0000001e,
0x00000002, 0x00040047, 0x0000004f, 0x0000001e, 0x00000003, 0x00040047, 0x00000055, 0x0000001e,
0x00000004, 0x00040047, 0x0000005a, 0x0000001e, 0x00000006, 0x00020013, 0x00000002, 0x00030021,
0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006,
0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, 0x00000009,
0x00000001, 0x0004001c, 0x0000000a, 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007,
0x00000006, 0x0000000a, 0x0000000a, 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b,
0x0000000c, 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b,
0x0000000e, 0x0000000f, 0x00000000, 0x00040018, 0x00000010, 0x00000007, 0x00000004, 0x0008001e,
0x00000011, 0x00000010, 0x00000010, 0x00000007, 0x00000007, 0x00000007, 0x00000007, 0x00040020,
0x00000012, 0x00000002, 0x00000011, 0x0004003b, 0x00000012, 0x00000013, 0x00000002, 0x00040020,
0x00000014, 0x00000002, 0x00000010, 0x00040017, 0x00000017, 0x00000006, 0x00000003, 0x00040020,
0x00000018, 0x00000001, 0x00000017, 0x0004003b, 0x00000018, 0x00000019, 0x00000001, 0x0004002b,
0x00000006, 0x0000001b, 0x3f800000, 0x00040020, 0x00000021, 0x00000003, 0x00000007, 0x00040020,
0x00000023, 0x00000007, 0x00000007, 0x0004002b, 0x0000000e, 0x00000025, 0x00000001, 0x00040020,
0x0000002e, 0x00000003, 0x00000017, 0x0004003b, 0x0000002e, 0x0000002f, 0x00000003, 0x00040018,
0x00000032, 0x00000017, 0x00000003, 0x00040020, 0x00000033, 0x00000007, 0x00000032, 0x0004003b,
0x0000002e, 0x0000003e, 0x00000003, 0x0004003b, 0x00000018, 0x00000040, 0x00000001, 0x00040017,
0x00000044, 0x00000006, 0x00000002, 0x00040020, 0x00000045, 0x00000003, 0x00000044, 0x0004003b,
0x00000045, 0x00000046, 0x00000003, 0x00040020, 0x00000047, 0x00000001, 0x00000044, 0x0004003b,
0x00000047, 0x00000048, 0x00000001, 0x0004003b, 0x00000021, 0x0000004a, 0x00000003, 0x0004002b,
0x0000000e, 0x0000004b, 0x00000004, 0x00040020, 0x0000004c, 0x00000002, 0x00000007, 0x0004003b,
0x0000002e, 0x0000004f, 0x00000003, 0x0004002b, 0x0000000e, 0x00000050, 0x00000002, 0x0004003b,
0x0000002e, 0x00000055, 0x00000003, 0x0004002b, 0x0000000e, 0x00000056, 0x00000003, 0x0004003b,
0x0000002e, 0x0000005a, 0x00000003, 0x0004002b, 0x0000000e, 0x0000005b, 0x00000005, 0x00050036,
0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x00000023,
0x00000024, 0x00000007, 0x0004003b, 0x00000033, 0x00000034, 0x00000007, 0x00050041, 0x00000014,
0x00000015, 0x00000013, 0x0000000f, 0x0004003d, 0x00000010, 0x00000016, 0x00000015, 0x0004003d,
0x00000017, 0x0000001a, 0x00000019, 0x00050051, 0x00000006, 0x0000001c, 0x0000001a, 0x00000000,
0x00050051, 0x00000006, 0x0000001d, 0x0000001a, 0x00000001, 0x00050051, 0x00000006, 0x0000001e,
0x0000001a, 0x00000002, 0x00070050, 0x00000007, 0x0000001f, 0x0000001c, 0x0000001d, 0x0000001e,
0x0000001b, 0x00050091, 0x00000007, 0x00000020, 0x00000016, 0x0000001f, 0x00050041, 0x00000021,
0x00000022, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000022, 0x00000020, 0x00050041, 0x00000014,
0x00000026, 0x00000013, 0x00000025, 0x0004003d, 0x00000010, 0x00000027, 0x00000026, 0x0004003d,
0x00000017, 0x00000028, 0x00000019, 0x00050051, 0x00000006, 0x00000029, 0x00000028, 0x00000000,
0x00050051, 0x00000006, 0x0000002a, 0x00000028, 0x00000001, 0x00050051, 0x00000006, 0x0000002b,
0x00000028, 0x00000002, 0x00070050, 0x00000007, 0x0000002c, 0x00000029, 0x0000002a, 0x0000002b,
0x0000001b, 0x00050091, 0x00000007, 0x0000002d, 0x00000027, 0x0000002c, 0x0003003e, 0x00000024,
0x0000002d, 0x0004003d, 0x00000007, 0x00000030, 0x00000024, 0x0008004f, 0x00000017, 0x00000031,
0x00000030, 0x00000030, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x0000002f, 0x00000031,
0x00050041, 0x00000014, 0x00000035, 0x00000013, 0x00000025, 0x0004003d, 0x00000010, 0x00000036,
0x00000035, 0x00050051, 0x00000007, 0x00000037, 0x00000036, 0x00000000, 0x0008004f, 0x00000017,
0x00000038, 0x00000037, 0x00000037, 0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000007,
0x00000039, 0x00000036, 0x00000001, 0x0008004f, 0x00000017, 0x0000003a, 0x00000039, 0x00000039,
0x00000000, 0x00000001, 0x00000002, 0x00050051, 0x00000007, 0x0000003b, 0x00000036, 0x00000002,
0x0008004f, 0x00000017, 0x0000003c, 0x0000003b, 0x0000003b, 0x00000000, 0x00000001, 0x00000002,
0x00060050, 0x00000032, 0x0000003d, 0x00000038, 0x0000003a, 0x0000003c, 0x0003003e, 0x00000034,
0x0000003d, 0x0004003d, 0x00000032, 0x0000003f, 0x00000034, 0x0004003d, 0x00000017, 0x00000041,
0x00000040, 0x00050091, 0x00000017, 0x00000042, 0x0000003f, 0x00000041, 0x0006000c, 0x00000017,
0x00000043, 0x00000001, 0x00000045, 0x00000042, 0x0003003e, 0x0000003e, 0x00000043, 0x0004003d,
0x00000044, 0x00000049, 0x00000048, 0x0003003e, 0x00000046, 0x00000049, 0x00050041, 0x0000004c,
0x0000004d, 0x00000013, 0x0000004b, 0x0004003d, 0x00000007, 0x0000004e, 0x0000004d, 0x0003003e,
0x0000004a, 0x0000004e, 0x00050041, 0x0000004c, 0x00000051, 0x00000013, 0x00000050, 0x0004003d,
0x00000007, 0x00000052, 0x00000051, 0x0008004f, 0x00000017, 0x00000053, 0x00000052, 0x00000052,
0x00000000, 0x00000001, 0x00000002, 0x0006000c, 0x00000017, 0x00000054, 0x00000001, 0x00000045,
0x00000053, 0x0003003e, 0x0000004f, 0x00000054, 0x00050041, 0x0000004c, 0x00000057, 0x00000013,
0x00000056, 0x0004003d, 0x00000007, 0x00000058, 0x00000057, 0x0008004f, 0x00000017, 0x00000059,
0x00000058, 0x00000058, 0x00000000, 0x00000001, 0x00000002, 0x0003003e, 0x00000055, 0x00000059,
0x00050041, 0x0000004c, 0x0000005c, 0x00000013, 0x0000005b, 0x0004003d, 0x00000007, 0x0000005d,
0x0000005c, 0x0008004f, 0x00000017, 0x0000005e, 0x0000005d, 0x0000005d, 0x00000000, 0x00000001,
0x00000002, 0x0003003e, 0x0000005a, 0x0000005e, 0x000100fd, 0x00010038,
};
static const size_t mesh3d_toon_vert_spv_count = sizeof(mesh3d_toon_vert_spv)/sizeof(mesh3d_toon_vert_spv[0]);
+305 -91
View File
@@ -8,9 +8,13 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <vector>
#if !defined(_WIN32)
#include <unistd.h>
#endif
#include "common/Exception.h"
#include "common/config.h"
@@ -44,13 +48,23 @@ Graphics::~Graphics() {
if (g->sampler) device->destroySampler(g->sampler);
}
ownedGpuTextures.clear();
for (auto &g : ownedGpuShaders) {
if (g->swapchainPipeline) device->destroyPipeline(g->swapchainPipeline);
if (g->offscreenPipeline) device->destroyPipeline(g->offscreenPipeline);
if (g->mesh3dPipeline) device->destroyPipeline(g->mesh3dPipeline);
// pipelineLayout is shared; do not destroy per-shader
}
ownedGpuShaders.clear();
ownedShaders.clear();
destroySwapchainResources();
if (pipeline) device->destroyPipeline(pipeline);
if (pipelineLayout) device->destroyPipelineLayout(pipelineLayout);
if (texPipeline) device->destroyPipeline(texPipeline);
if (texPipelineLayout) device->destroyPipelineLayout(texPipelineLayout);
if (shaderPipelineLayout) device->destroyPipelineLayout(shaderPipelineLayout);
if (mesh3dPipeline) device->destroyPipeline(mesh3dPipeline);
if (mesh3dPipelineLayout) device->destroyPipelineLayout(mesh3dPipelineLayout);
if (mesh3dShaderPipelineLayout) device->destroyPipelineLayout(mesh3dShaderPipelineLayout);
mesh3dSetLayoutUnique.reset();
mesh3dUboSlots.clear();
if (offscreenSolidPipeline) device->destroyPipeline(offscreenSolidPipeline);
@@ -342,8 +356,25 @@ void Graphics::createTexturedPipeline() {
plInfo.pSetLayouts = &texSetLayout;
texPipelineLayout = device->createPipelineLayout(plInfo);
vk::PushConstantRange pcr{};
pcr.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
pcr.offset = 0;
pcr.size = Shader::kPushConstantBytes;
vk::PipelineLayoutCreateInfo shaderPlInfo{};
shaderPlInfo.setLayoutCount = 1;
shaderPlInfo.pSetLayouts = &texSetLayout;
shaderPlInfo.pushConstantRangeCount = 1;
shaderPlInfo.pPushConstantRanges = &pcr;
shaderPipelineLayout = device->createPipelineLayout(shaderPlInfo);
std::vector<uint32_t> vert(textured_vert_spv, textured_vert_spv + textured_vert_spv_count);
std::vector<uint32_t> frag(textured_frag_spv, textured_frag_spv + textured_frag_spv_count);
texPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout);
}
vk::Pipeline Graphics::createTexturedStylePipeline(const std::vector<uint32_t> &vert,
const std::vector<uint32_t> &frag,
vk::RenderPass rp, vk::PipelineLayout layout) {
vk::ShaderModule vertModule =
vkb::PipelineBuilder::createShaderModule(device.instance, vert);
vk::ShaderModule fragModule =
@@ -416,17 +447,16 @@ void Graphics::createTexturedPipeline() {
pci.pDepthStencilState = &ds;
pci.pColorBlendState = &blend;
pci.pDynamicState = &dyn;
pci.layout = texPipelineLayout;
pci.renderPass = renderpass;
pci.layout = layout;
pci.renderPass = rp;
pci.subpass = 0;
auto result = device->createGraphicsPipeline(nullptr, pci);
if (result.result != vk::Result::eSuccess)
throw Exception("failed to create textured pipeline");
texPipeline = result.value;
device->destroyShaderModule(vertModule);
device->destroyShaderModule(fragModule);
if (result.result != vk::Result::eSuccess)
throw Exception("failed to create textured-style pipeline");
return result.value;
}
void Graphics::createMesh3DPipeline() {
@@ -447,12 +477,29 @@ void Graphics::createMesh3DPipeline() {
plInfo.pSetLayouts = &mesh3dSetLayout;
mesh3dPipelineLayout = device->createPipelineLayout(plInfo);
vk::PushConstantRange pcr{};
pcr.stageFlags = vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment;
pcr.offset = 0;
pcr.size = Shader::kPushConstantBytes;
vk::PipelineLayoutCreateInfo shaderPl{};
shaderPl.setLayoutCount = 1;
shaderPl.pSetLayouts = &mesh3dSetLayout;
shaderPl.pushConstantRangeCount = 1;
shaderPl.pPushConstantRanges = &pcr;
mesh3dShaderPipelineLayout = device->createPipelineLayout(shaderPl);
// UBO + descriptor sets are allocated lazily per draw (see mesh3dSetFor).
mesh3dUboSlots.clear();
mesh3dDrawIndex = 0;
std::vector<uint32_t> vert(mesh3d_vert_spv, mesh3d_vert_spv + mesh3d_vert_spv_count);
std::vector<uint32_t> frag(mesh3d_frag_spv, mesh3d_frag_spv + mesh3d_frag_spv_count);
mesh3dPipeline = createMesh3DStylePipeline(vert, frag, mesh3dPipelineLayout);
}
vk::Pipeline Graphics::createMesh3DStylePipeline(const std::vector<uint32_t> &vert,
const std::vector<uint32_t> &frag,
vk::PipelineLayout layout) {
vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert);
vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag);
@@ -518,17 +565,16 @@ void Graphics::createMesh3DPipeline() {
pci.pDepthStencilState = &ds;
pci.pColorBlendState = &blend;
pci.pDynamicState = &dyn;
pci.layout = mesh3dPipelineLayout;
pci.layout = layout;
pci.renderPass = renderpass;
pci.subpass = 0;
auto result = device->createGraphicsPipeline(nullptr, pci);
if (result.result != vk::Result::eSuccess)
throw Exception("failed to create mesh3d pipeline");
mesh3dPipeline = result.value;
device->destroyShaderModule(vertModule);
device->destroyShaderModule(fragModule);
if (result.result != vk::Result::eSuccess)
throw Exception("failed to create mesh3d-style pipeline");
return result.value;
}
void Graphics::ensureOffscreenPipelines() {
@@ -566,74 +612,19 @@ void Graphics::ensureOffscreenPipelines() {
// Textured offscreen pipeline mirrors createTexturedPipeline but with offscreen RP.
std::vector<uint32_t> tvert(textured_vert_spv, textured_vert_spv + textured_vert_spv_count);
std::vector<uint32_t> tfrag(textured_frag_spv, textured_frag_spv + textured_frag_spv_count);
vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, tvert);
vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, tfrag);
offscreenTexPipeline =
createTexturedStylePipeline(tvert, tfrag, offscreenRenderPass, texPipelineLayout);
vk::PipelineShaderStageCreateInfo stages[2]{};
stages[0].stage = vk::ShaderStageFlagBits::eVertex;
stages[0].module = vertModule;
stages[0].pName = "main";
stages[1].stage = vk::ShaderStageFlagBits::eFragment;
stages[1].module = fragModule;
stages[1].pName = "main";
// Lazily create offscreen pipelines for any custom shaders already loaded.
for (auto &sh : ownedShaders) ensureShaderOffscreenPipeline(sh.get());
}
auto binding = TexturedVertex::getBindingDescription(0);
auto attrs = TexturedVertex::getAttributeDescription(0);
vk::PipelineVertexInputStateCreateInfo vi{};
vi.vertexBindingDescriptionCount = 1;
vi.pVertexBindingDescriptions = &binding;
vi.vertexAttributeDescriptionCount = uint32_t(attrs.size());
vi.pVertexAttributeDescriptions = attrs.data();
vk::PipelineInputAssemblyStateCreateInfo ia{};
ia.topology = vk::PrimitiveTopology::eTriangleList;
vk::PipelineViewportStateCreateInfo vp{};
vp.viewportCount = 1;
vp.scissorCount = 1;
vk::PipelineRasterizationStateCreateInfo rs{};
rs.polygonMode = vk::PolygonMode::eFill;
rs.cullMode = vk::CullModeFlagBits::eNone;
rs.frontFace = vk::FrontFace::eCounterClockwise;
rs.lineWidth = 1.0f;
vk::PipelineMultisampleStateCreateInfo ms{};
ms.rasterizationSamples = vk::SampleCountFlagBits::e1;
vk::PipelineColorBlendAttachmentState blendAtt{};
blendAtt.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
blendAtt.blendEnable = true;
blendAtt.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha;
blendAtt.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
blendAtt.colorBlendOp = vk::BlendOp::eAdd;
blendAtt.srcAlphaBlendFactor = vk::BlendFactor::eOne;
blendAtt.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
blendAtt.alphaBlendOp = vk::BlendOp::eAdd;
vk::PipelineColorBlendStateCreateInfo blend{};
blend.attachmentCount = 1;
blend.pAttachments = &blendAtt;
vk::DynamicState dynStates[] = {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dyn{};
dyn.dynamicStateCount = 2;
dyn.pDynamicStates = dynStates;
vk::GraphicsPipelineCreateInfo pci{};
pci.stageCount = 2;
pci.pStages = stages;
pci.pVertexInputState = &vi;
pci.pInputAssemblyState = &ia;
pci.pViewportState = &vp;
pci.pRasterizationState = &rs;
pci.pMultisampleState = &ms;
pci.pColorBlendState = &blend;
pci.pDynamicState = &dyn;
pci.layout = texPipelineLayout;
pci.renderPass = offscreenRenderPass;
pci.subpass = 0;
auto result = device->createGraphicsPipeline(nullptr, pci);
if (result.result != vk::Result::eSuccess)
throw Exception("failed to create offscreen textured pipeline");
offscreenTexPipeline = result.value;
device->destroyShaderModule(vertModule);
device->destroyShaderModule(fragModule);
void Graphics::ensureShaderOffscreenPipeline(Shader *shader) {
if (!shader || !shader->gpuHandle || !offscreenRenderPass) return;
auto *gpu = static_cast<GpuShader *>(shader->gpuHandle);
if (gpu->offscreenPipeline) return;
gpu->offscreenPipeline = createTexturedStylePipeline(shader->vertexSpirv(), shader->fragmentSpirv(),
offscreenRenderPass, shaderPipelineLayout);
}
Texture *Graphics::getTexture() { return nullptr; }
@@ -884,16 +875,187 @@ Texture *Graphics::newTextureFromFile(const std::string &filename) {
}
void Graphics::drawTexturedRect(Texture *texture, float x, float y, float w, float h, const Color &color) {
drawTexturedRectShader(texture, currentShader, x, y, w, h, color);
}
void Graphics::drawTexturedRectShader(Texture *texture, Shader *shader, float x, float y, float w,
float h, const Color &color) {
if (!texture) {
drawSolidRect(x, y, w, h, color);
return;
}
if (texturedBatches.empty() || texturedBatches.back().texture != texture) {
texturedBatches.push_back(TexturedBatch{texture, Batcher{}});
if (texturedBatches.empty() || texturedBatches.back().texture != texture ||
texturedBatches.back().shader != shader) {
texturedBatches.push_back(TexturedBatch{texture, shader, Batcher{}});
}
texturedBatches.back().batch.addTexturedRect(x, y, w, h, color, 0, 0, 1, 1);
}
namespace {
std::vector<uint32_t> loadSpirvBytes(const void *data, size_t size) {
if (!data || size < 4 || (size % 4) != 0)
throw Exception("SPIR-V: invalid size %zu", size);
const auto *words = static_cast<const uint32_t *>(data);
if (words[0] != 0x07230203)
throw Exception("SPIR-V: bad magic (expected 0x07230203)");
return std::vector<uint32_t>(words, words + size / 4);
}
std::vector<uint32_t> readSpirvFile(const std::string &path) {
auto *fs = filesystem::Filesystem::create();
std::unique_ptr<filesystem::FileData> fd(fs->read(path));
if (!fd) throw Exception("newShaderFromSpvFile: failed to read '%s'", path.c_str());
return loadSpirvBytes(fd->getData(), fd->getSize());
}
std::vector<uint32_t> compileGlslWithGlslc(const std::string &source, const char *stage) {
if (source.empty()) throw Exception("newShader: empty %s GLSL", stage);
#if defined(_WIN32)
(void)source;
(void)stage;
throw Exception("newShader: GLSL compile via glslc is not supported on Windows; "
"use newShaderFromSpv / newShaderFromSpvFile");
#else
char inPath[] = "/tmp/eve_shader_XXXXXX";
int fd = mkstemp(inPath);
if (fd < 0) throw Exception("newShader: mkstemp failed");
std::string outPath = std::string(inPath) + ".spv";
{
ssize_t n = write(fd, source.data(), source.size());
close(fd);
if (n < 0 || size_t(n) != source.size()) {
unlink(inPath);
throw Exception("newShader: failed to write temp GLSL");
}
}
std::string cmd = std::string("glslc -fshader-stage=") + stage + " \"" + inPath + "\" -o \"" +
outPath + "\" 2>&1";
FILE *pipe = popen(cmd.c_str(), "r");
std::string err;
if (pipe) {
char buf[256];
while (fgets(buf, sizeof(buf), pipe)) err += buf;
int status = pclose(pipe);
unlink(inPath);
if (status != 0) {
unlink(outPath.c_str());
throw Exception("newShader: glslc failed for %s:\n%s", stage, err.c_str());
}
} else {
unlink(inPath);
throw Exception("newShader: glslc not available (popen failed)");
}
FILE *f = fopen(outPath.c_str(), "rb");
if (!f) {
unlink(outPath.c_str());
throw Exception("newShader: failed to open compiled SPIR-V");
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
std::vector<uint8_t> bytes(static_cast<size_t>(sz > 0 ? sz : 0));
if (sz > 0 && fread(bytes.data(), 1, static_cast<size_t>(sz), f) != static_cast<size_t>(sz)) {
fclose(f);
unlink(outPath.c_str());
throw Exception("newShader: failed to read compiled SPIR-V");
}
fclose(f);
unlink(outPath.c_str());
return loadSpirvBytes(bytes.data(), bytes.size());
#endif
}
} // namespace
Shader *Graphics::newShaderFromSpv(const std::vector<uint32_t> &vertSpv,
const std::vector<uint32_t> &fragSpv) {
ASSERT(initialized);
if (!initialized) throw Exception("newShaderFromSpv: graphics not initialized");
if (fragSpv.empty()) throw Exception("newShaderFromSpv: empty fragment SPIR-V");
std::vector<uint32_t> vert = vertSpv;
if (vert.empty())
vert.assign(textured_vert_spv, textured_vert_spv + textured_vert_spv_count);
if (vert[0] != 0x07230203 || fragSpv[0] != 0x07230203)
throw Exception("newShaderFromSpv: SPIR-V magic mismatch");
auto gpu = std::make_unique<GpuShader>();
gpu->pipelineLayout = shaderPipelineLayout;
gpu->swapchainPipeline =
createTexturedStylePipeline(vert, fragSpv, renderpass, shaderPipelineLayout);
if (offscreenRenderPass) {
gpu->offscreenPipeline =
createTexturedStylePipeline(vert, fragSpv, offscreenRenderPass, shaderPipelineLayout);
}
auto sh = std::make_unique<Shader>();
sh->setSpirv(std::move(vert), fragSpv);
sh->gpuHandle = gpu.get();
Shader *raw = sh.get();
ownedShaders.push_back(std::move(sh));
ownedGpuShaders.push_back(std::move(gpu));
return raw;
}
Shader *Graphics::newShaderFromSpvFile(const std::string &vertPath, const std::string &fragPath) {
if (fragPath.empty()) throw Exception("newShaderFromSpvFile: empty fragPath");
std::vector<uint32_t> vert;
if (!vertPath.empty()) vert = readSpirvFile(vertPath);
auto frag = readSpirvFile(fragPath);
return newShaderFromSpv(vert, frag);
}
Shader *Graphics::newShader(const std::string &vertGlsl, const std::string &fragGlsl) {
if (fragGlsl.empty()) throw Exception("newShader: empty fragment GLSL");
std::vector<uint32_t> vert;
if (!vertGlsl.empty()) vert = compileGlslWithGlslc(vertGlsl, "vert");
auto frag = compileGlslWithGlslc(fragGlsl, "frag");
return newShaderFromSpv(vert, frag);
}
Shader *Graphics::newMeshShaderFromSpv(const std::vector<uint32_t> &vertSpv,
const std::vector<uint32_t> &fragSpv) {
ASSERT(initialized);
if (!initialized) throw Exception("newMeshShaderFromSpv: graphics not initialized");
if (fragSpv.empty()) throw Exception("newMeshShaderFromSpv: empty fragment SPIR-V");
if (!mesh3dShaderPipelineLayout)
throw Exception("newMeshShaderFromSpv: mesh3d pipeline layout missing");
std::vector<uint32_t> vert = vertSpv;
if (vert.empty())
vert.assign(mesh3d_vert_spv, mesh3d_vert_spv + mesh3d_vert_spv_count);
if (vert[0] != 0x07230203 || fragSpv[0] != 0x07230203)
throw Exception("newMeshShaderFromSpv: SPIR-V magic mismatch");
auto gpu = std::make_unique<GpuShader>();
gpu->isMesh3D = true;
gpu->pipelineLayout = mesh3dShaderPipelineLayout;
gpu->mesh3dPipeline =
createMesh3DStylePipeline(vert, fragSpv, mesh3dShaderPipelineLayout);
auto sh = std::make_unique<Shader>();
sh->setKind(Shader::Kind::eMesh3D);
sh->setSpirv(std::move(vert), fragSpv);
sh->gpuHandle = gpu.get();
Shader *raw = sh.get();
ownedShaders.push_back(std::move(sh));
ownedGpuShaders.push_back(std::move(gpu));
return raw;
}
Shader *Graphics::newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) {
if (fragGlsl.empty()) throw Exception("newMeshShader: empty fragment GLSL");
std::vector<uint32_t> vert;
if (!vertGlsl.empty()) vert = compileGlslWithGlslc(vertGlsl, "vert");
auto frag = compileGlslWithGlslc(fragGlsl, "frag");
return newMeshShaderFromSpv(vert, frag);
}
void Graphics::flushBatch() {
if (!initialized) return;
if (isCanvasActive()) {
@@ -967,9 +1129,28 @@ void Graphics::flushToOffscreen(OffscreenCanvas *canvas) {
gpuVerts.push_back(TexturedVertex{v.pos, v.color, v.uv});
texBufs.emplace_back();
texBufs.back().allocate<TexturedVertex>(device, gpuVerts);
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, offscreenTexPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, texPipelineLayout, 0, 1,
&gpu->descriptorSet, 0, nullptr);
if (tb.shader && tb.shader->gpuHandle) {
ensureShaderOffscreenPipeline(tb.shader);
auto *gs = static_cast<GpuShader *>(tb.shader->gpuHandle);
if (!gs->offscreenPipeline) continue;
cb.bindPipeline(vk::PipelineBindPoint::eGraphics,
gs->offscreenPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
shaderPipelineLayout, 0, 1,
&gpu->descriptorSet, 0, nullptr);
cb.pushConstants(shaderPipelineLayout,
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment,
0, Shader::kPushConstantBytes,
tb.shader->pushConstantData());
} else {
cb.bindPipeline(vk::PipelineBindPoint::eGraphics,
offscreenTexPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
texPipelineLayout, 0, 1,
&gpu->descriptorSet, 0, nullptr);
}
vk::DeviceSize offset = 0;
cb.bindVertexBuffers(0, 1, texBufs.back(), &offset);
cb.draw(uint32_t(gpuVerts.size()), 1, 0, 0);
@@ -1039,9 +1220,20 @@ void Graphics::flushToSwapchain() {
texBufs.emplace_back();
texBufs.back().allocate<TexturedVertex>(device, gpuVerts);
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, texPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, texPipelineLayout, 0, 1,
&gpu->descriptorSet, 0, nullptr);
if (tb.shader && tb.shader->gpuHandle) {
auto *gs = static_cast<GpuShader *>(tb.shader->gpuHandle);
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, gs->swapchainPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, shaderPipelineLayout, 0, 1,
&gpu->descriptorSet, 0, nullptr);
cb.pushConstants(shaderPipelineLayout,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
Shader::kPushConstantBytes, tb.shader->pushConstantData());
} else {
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, texPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, texPipelineLayout, 0, 1,
&gpu->descriptorSet, 0, nullptr);
}
vk::DeviceSize offset = 0;
cb.bindVertexBuffers(0, 1, texBufs.back(), &offset);
cb.draw(uint32_t(gpuVerts.size()), 1, 0, 0);
@@ -1100,6 +1292,10 @@ void Graphics::setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) {
mesh3dFrameUbo.lightColor = glm::vec4(color, 1.f);
}
void Graphics::setMesh3DCameraPos(const glm::vec3 &eye) {
mesh3dFrameUbo.cameraPos = glm::vec4(eye, 0.f);
}
vk::DescriptorSet Graphics::mesh3dSetFor(GpuTexture *gpuTex, size_t uboSlot) {
ASSERT(gpuTex != nullptr);
while (mesh3dUboSlots.size() <= uboSlot) {
@@ -1280,6 +1476,11 @@ Mesh *Graphics::newMeshSphere(int slices, int stacks) {
}
void Graphics::drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) {
drawMeshShader(mesh, model, texture, tint, nullptr);
}
void Graphics::drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint,
Shader *shader) {
ASSERT(initialized);
ASSERT(mesh != nullptr);
if (!initialized) throw Exception("drawMesh: graphics not initialized");
@@ -1287,14 +1488,17 @@ void Graphics::drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, co
if (!swapchainPassOpen) throw Exception("drawMesh: call begin3DFrame first");
if (!mesh3dPipeline) throw Exception("drawMesh: mesh3d pipeline missing");
if (shader) {
if (shader->getKind() != Shader::Kind::eMesh3D)
throw Exception("drawMesh: shader is not a Mesh3D shader (use newMeshShader*)");
if (!shader->gpuHandle) throw Exception("drawMesh: shader has no GPU pipeline");
}
auto *gpuMesh = static_cast<GpuMesh *>(mesh->gpuHandle);
Texture *tex = texture ? texture : whiteTexture;
if (!tex || !tex->gpuHandle) throw Exception("drawMesh: missing texture");
auto *gpuTex = static_cast<GpuTexture *>(tex->gpuHandle);
// Caller supplies view*proj via model? No — store viewProj in mesh3dFrameUbo.mvp temporarily.
// drawMesh receives model; mvp = viewProj * model where viewProj is in mesh3dFrameUbo.mvp
// before multiply. RenderSystem3D sets mesh3dFrameUbo.mvp = viewProj first via setMesh3DViewProj.
Mesh3DUBO ubo = mesh3dFrameUbo;
ubo.model = model;
ubo.mvp = mesh3dFrameUbo.mvp * model;
@@ -1305,9 +1509,19 @@ void Graphics::drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, co
mesh3dUboSlots[slot].ubo.updateLocal(ubo);
auto &cb = presentModel.getCurrentCommandBuffer();
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dPipelineLayout, 0, 1, &set, 0,
nullptr);
if (shader) {
auto *gs = static_cast<GpuShader *>(shader->gpuHandle);
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, gs->mesh3dPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dShaderPipelineLayout, 0, 1, &set,
0, nullptr);
cb.pushConstants(mesh3dShaderPipelineLayout,
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
Shader::kPushConstantBytes, shader->pushConstantData());
} else {
cb.bindPipeline(vk::PipelineBindPoint::eGraphics, mesh3dPipeline);
cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, mesh3dPipelineLayout, 0, 1, &set, 0,
nullptr);
}
vk::DeviceSize offset = 0;
cb.bindVertexBuffers(0, 1, gpuMesh->vertices, &offset);
cb.bindIndexBuffer(gpuMesh->indices.buffer, 0, vk::IndexType::eUint32);
+34
View File
@@ -3,6 +3,7 @@
#include "graphics/Batcher.h"
#include "graphics/Texture.h"
#include "graphics/Mesh.h"
#include "graphics/Shader.h"
#include "vkbuilder.hpp"
#include <atomic>
#include <memory>
@@ -82,6 +83,7 @@ struct Mesh3DUBO {
glm::vec4 lightDir{0.4f, 1.f, 0.3f, 0.f};
glm::vec4 lightColor{1.f, 1.f, 1.f, 1.f};
glm::vec4 tint{1.f, 1.f, 1.f, 1.f};
glm::vec4 cameraPos{0.f, 0.f, 3.f, 0.f}; // xyz = eye (for rim / custom shaders)
};
struct GpuTexture {
@@ -98,6 +100,14 @@ struct GpuMesh {
uint32_t indexCount = 0;
};
struct GpuShader {
vk::Pipeline swapchainPipeline;
vk::Pipeline offscreenPipeline;
vk::Pipeline mesh3dPipeline;
vk::PipelineLayout pipelineLayout;
bool isMesh3D = false;
};
class Graphics final : public eve::graphics::Graphics {
public:
~Graphics() override;
@@ -112,12 +122,24 @@ public:
Texture *newTexture(image::ImageData *data) override;
Texture *newTextureFromFile(const std::string &filename) override;
void drawTexturedRect(Texture *texture, float x, float y, float w, float h, const Color &color) override;
void drawTexturedRectShader(Texture *texture, Shader *shader, float x, float y, float w, float h,
const Color &color) override;
Shader *newShaderFromSpv(const std::vector<uint32_t> &vertSpv,
const std::vector<uint32_t> &fragSpv) override;
Shader *newShaderFromSpvFile(const std::string &vertPath, const std::string &fragPath) override;
Shader *newShader(const std::string &vertGlsl, const std::string &fragGlsl) override;
Shader *newMeshShaderFromSpv(const std::vector<uint32_t> &vertSpv,
const std::vector<uint32_t> &fragSpv) override;
Shader *newMeshShader(const std::string &vertGlsl, const std::string &fragGlsl) override;
Mesh *newMeshFromAssimp(const ::aiMesh &mesh) override;
Mesh *newMeshSphere(int slices = 32, int stacks = 16) override;
void begin3DFrame() override;
void setMesh3DViewProj(const glm::mat4 &viewProj) override;
void drawMesh(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint) override;
void drawMeshShader(Mesh *mesh, const glm::mat4 &model, Texture *texture, const Color &tint,
Shader *shader) override;
void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) override;
void setMesh3DCameraPos(const glm::vec3 &eye) override;
Canvas *newCanvas(int width, int height) override;
void setCanvas(Canvas *canvas) override;
@@ -150,6 +172,13 @@ private:
void createTexturedPipeline();
void createMesh3DPipeline();
void ensureOffscreenPipelines();
void ensureShaderOffscreenPipeline(Shader *shader);
vk::Pipeline createTexturedStylePipeline(const std::vector<uint32_t> &vert,
const std::vector<uint32_t> &frag,
vk::RenderPass rp, vk::PipelineLayout layout);
vk::Pipeline createMesh3DStylePipeline(const std::vector<uint32_t> &vert,
const std::vector<uint32_t> &frag,
vk::PipelineLayout layout);
void destroySwapchainResources();
void flushBatch();
void flushToSwapchain();
@@ -198,6 +227,7 @@ private:
vk::DescriptorPool descriptorPool;
vk::Pipeline texPipeline;
vk::PipelineLayout texPipelineLayout;
vk::PipelineLayout shaderPipelineLayout; // tex set + push constants
vk::CommandPool uploadPool;
vk::RenderPass offscreenRenderPass;
@@ -207,6 +237,7 @@ private:
vk::DescriptorSetLayout mesh3dSetLayout;
vk::UniqueDescriptorSetLayout mesh3dSetLayoutUnique;
vk::PipelineLayout mesh3dPipelineLayout;
vk::PipelineLayout mesh3dShaderPipelineLayout; // + push constants for custom mesh shaders
vk::Pipeline mesh3dPipeline;
// One UBO (+ per-texture descriptor sets) per draw in the current 3D frame.
// Avoids vkUpdateDescriptorSets on a set already bound in a recording /
@@ -226,6 +257,7 @@ private:
Batcher solidBatch;
struct TexturedBatch {
Texture *texture = nullptr;
Shader *shader = nullptr;
Batcher batch;
};
std::vector<TexturedBatch> texturedBatches;
@@ -237,6 +269,8 @@ private:
std::vector<std::unique_ptr<GpuTexture>> ownedGpuTextures;
std::vector<std::unique_ptr<Mesh>> ownedMeshes;
std::vector<std::unique_ptr<GpuMesh>> ownedGpuMeshes;
std::vector<std::unique_ptr<Shader>> ownedShaders;
std::vector<std::unique_ptr<GpuShader>> ownedGpuShaders;
std::vector<std::unique_ptr<eve::graphics::Canvas>> ownedCanvases;
bool swapchainPassOpen = false;
+124
View File
@@ -0,0 +1,124 @@
#include "model3d/EveFileSystem.h"
#include "filesystem/File.h"
#include "filesystem/Filesystem.h"
#include <cstdio>
namespace eve {
namespace model3d {
namespace {
struct FileHandle {
filesystem::File *file = nullptr;
};
} // namespace
EveFileSystem::EveFileSystem(filesystem::Filesystem *fs) : fs_(fs) {}
medialoader::FileHandle *EveFileSystem::open(const char *path, const char *mode) {
if (!fs_ || !path || !mode)
return nullptr;
try {
filesystem::File *file = fs_->newFile(path);
if (!file)
return nullptr;
if (!file->open(mode)) {
delete file;
return nullptr;
}
auto *h = new FileHandle();
h->file = file;
return reinterpret_cast<medialoader::FileHandle *>(h);
} catch (...) {
return nullptr;
}
}
size_t EveFileSystem::read(medialoader::FileHandle *h, void *buf, size_t size) {
auto *fh = reinterpret_cast<FileHandle *>(h);
if (!fh || !fh->file || !buf || size == 0)
return 0;
try {
int64_t n = fh->file->read(buf, static_cast<int64_t>(size));
return n > 0 ? static_cast<size_t>(n) : 0;
} catch (...) {
return 0;
}
}
bool EveFileSystem::seek(medialoader::FileHandle *h, int64_t offset, int whence) {
auto *fh = reinterpret_cast<FileHandle *>(h);
if (!fh || !fh->file)
return false;
try {
int64_t pos = 0;
if (whence == SEEK_SET)
pos = offset;
else if (whence == SEEK_CUR)
pos = fh->file->tell() + offset;
else if (whence == SEEK_END)
pos = fh->file->getSize() + offset;
else
return false;
if (pos < 0)
return false;
return fh->file->seek(static_cast<uint64_t>(pos));
} catch (...) {
return false;
}
}
int64_t EveFileSystem::tell(medialoader::FileHandle *h) {
auto *fh = reinterpret_cast<FileHandle *>(h);
if (!fh || !fh->file)
return -1;
try {
return fh->file->tell();
} catch (...) {
return -1;
}
}
int64_t EveFileSystem::size(medialoader::FileHandle *h) {
auto *fh = reinterpret_cast<FileHandle *>(h);
if (!fh || !fh->file)
return -1;
try {
return fh->file->getSize();
} catch (...) {
return -1;
}
}
void EveFileSystem::close(medialoader::FileHandle *h) {
auto *fh = reinterpret_cast<FileHandle *>(h);
if (!fh)
return;
if (fh->file) {
try {
if (fh->file->isOpen())
fh->file->close();
} catch (...) {
}
delete fh->file;
fh->file = nullptr;
}
delete fh;
}
bool EveFileSystem::exists(const char *path) const {
if (!fs_ || !path)
return false;
try {
filesystem::Filesystem::Info info;
return fs_->getInfo(path, info);
} catch (...) {
return false;
}
}
} // namespace model3d
} // namespace eve
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "medialoader/model/FileSystem.h"
namespace eve {
namespace filesystem {
class Filesystem;
}
namespace model3d {
/** medialoader::FileSystem adapter over eve::filesystem (physfs / VFS). */
class EveFileSystem : public medialoader::FileSystem {
public:
explicit EveFileSystem(filesystem::Filesystem *fs);
medialoader::FileHandle *open(const char *path, const char *mode) override;
size_t read(medialoader::FileHandle *h, void *buf, size_t size) override;
bool seek(medialoader::FileHandle *h, int64_t offset, int whence) override;
int64_t tell(medialoader::FileHandle *h) override;
int64_t size(medialoader::FileHandle *h) override;
void close(medialoader::FileHandle *h) override;
bool exists(const char *path) const override;
private:
filesystem::Filesystem *fs_;
};
} // namespace model3d
} // namespace eve
+125
View File
@@ -0,0 +1,125 @@
#include "model3d/Model3D.h"
#include "model3d/EveFileSystem.h"
#include "common/Data.h"
#include "common/Exception.h"
#include "filesystem/FileData.h"
#include "filesystem/Filesystem.h"
#include "medialoader/Exception.h"
#include "medialoader/model/ModelLoader.h"
#include <simplesquirrel/simplesquirrel.hpp>
#include <cctype>
#include <functional>
namespace eve {
namespace model3d {
Module_IMPL(Model3D, new Model3D());
Model3D::Model3D() = default;
Model3D::~Model3D() = default;
namespace {
std::string lowerExt(std::string ext) {
for (char &c : ext)
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
return ext;
}
std::string ensureDotExt(std::string ext) {
ext = lowerExt(std::move(ext));
if (ext.empty())
return {};
if (ext[0] != '.')
ext.insert(ext.begin(), '.');
return ext;
}
medialoader::ModelScene loadOrThrow(medialoader::ModelLoader &loader, const void *data, size_t size,
const char *hint) {
try {
return loader.loadFromMemory(data, size, hint);
} catch (const medialoader::Exception &e) {
throw eve::Exception("%s", e.what());
}
}
} // namespace
ModelData *Model3D::newModelData(Data *data, std::string hintExt) {
if (data == nullptr || data->getData() == nullptr || data->getSize() == 0)
throw eve::Exception("Cannot decode empty model data");
std::string hint = ensureDotExt(std::move(hintExt));
if (hint.empty()) {
if (auto *fd = dynamic_cast<filesystem::FileData *>(data))
hint = ensureDotExt(fd->getExtension());
}
// Prefer VFS for sidecar resolution when FileData carries a filename.
filesystem::Filesystem *fs = ModuleManager::getInstance<filesystem::Filesystem>("Filesystem");
if (!fs)
fs = filesystem::Filesystem::create();
EveFileSystem eveFs(fs);
medialoader::ModelLoader loader(&eveFs);
auto scene = loadOrThrow(loader, data->getData(), data->getSize(), hint.c_str());
if (scene.empty())
throw eve::Exception("Could not decode model data");
std::string uri;
if (auto *fd = dynamic_cast<filesystem::FileData *>(data))
uri = "file://" + fd->getFilename();
return new ModelData(std::move(scene), std::move(uri));
}
ModelData *Model3D::newModelDataFromFile(std::string path) {
if (path.empty())
throw eve::Exception("Model3D::newModelDataFromFile: empty path");
filesystem::Filesystem *fs = ModuleManager::getInstance<filesystem::Filesystem>("Filesystem");
if (!fs)
fs = filesystem::Filesystem::create();
EveFileSystem eveFs(fs);
medialoader::ModelLoader loader(&eveFs);
try {
auto scene = loader.loadFromPath(path.c_str());
if (scene.empty())
throw eve::Exception("Could not load model: %s", path.c_str());
return new ModelData(std::move(scene), "file://" + path);
} catch (const medialoader::Exception &e) {
throw eve::Exception("%s", e.what());
}
}
void Model3D::expose(ssq::Table &table) {
auto cls = table.addClass(name, Model3D::create, false);
expose(cls);
auto md = table.addClass<ModelData>(
"ModelData", std::function<ModelData *()>([]() -> ModelData * { return nullptr; }), true);
md.addFunc("empty", &ModelData::empty);
md.addFunc("getMeshCount", &ModelData::getMeshCount);
md.addFunc("getMaterialCount", &ModelData::getMaterialCount);
md.addFunc("getVertexCount", &ModelData::getVertexCount);
md.addFunc("getFaceCount", &ModelData::getFaceCount);
md.addFunc("hasNormals", &ModelData::hasNormals);
md.addFunc("hasTexCoords", &ModelData::hasTexCoords);
}
void Model3D::expose(ssq::Class &cls) {
cls.addFunc("getName", &Model3D::getName);
cls.addFunc("newModelData", &Model3D::newModelData);
cls.addFunc("newModelDataFromFile", &Model3D::newModelDataFromFile);
}
} // namespace model3d
} // namespace eve
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include "common/Module.h"
#include "model3d/ModelData.h"
#include <string>
namespace eve {
class Data;
namespace model3d {
/**
* Resource module for decoding 3D models via medialoader (Assimp).
* Produces ModelData; GPU upload is graphics' responsibility.
*/
class Model3D : public Module {
public:
Module_REG(Model3D);
Model3D();
~Model3D() override;
/**
* Decode model from in-memory bytes.
* @param data Encoded model bytes (e.g. FileData / ByteData).
* @param hintExt Extension hint for Assimp (e.g. ".obj"). If empty and
* data is FileData, uses FileData::getExtension().
*/
ModelData *newModelData(Data *data, std::string hintExt = "");
/**
* Decode model from a VFS path (physfs). Uses EveFileSystem so sidecar
* files (.mtl, textures) resolve through the same filesystem.
*/
ModelData *newModelDataFromFile(std::string path);
};
} // namespace model3d
} // namespace eve
+72
View File
@@ -0,0 +1,72 @@
#include "model3d/ModelData.h"
#include "common/Exception.h"
#include <assimp/mesh.h>
#include <assimp/scene.h>
namespace eve {
namespace model3d {
ModelData::ModelData(medialoader::ModelScene scene, std::string uri)
: Resource(std::move(uri)), scene(std::move(scene)) {}
ModelData::~ModelData() = default;
bool ModelData::empty() const { return scene.empty(); }
const aiScene *ModelData::getScene() const {
if (scene.empty())
return nullptr;
return scene.get();
}
const aiMesh *ModelData::meshAt(int meshIndex) const {
const aiScene *sc = getScene();
if (!sc || meshIndex < 0 || static_cast<unsigned>(meshIndex) >= sc->mNumMeshes)
return nullptr;
return sc->mMeshes[meshIndex];
}
const aiMesh *ModelData::getMesh(int meshIndex) const { return meshAt(meshIndex); }
int ModelData::getMeshCount() const {
const aiScene *sc = getScene();
return sc ? static_cast<int>(sc->mNumMeshes) : 0;
}
int ModelData::getMaterialCount() const {
const aiScene *sc = getScene();
return sc ? static_cast<int>(sc->mNumMaterials) : 0;
}
int ModelData::getVertexCount(int meshIndex) const {
const aiMesh *m = meshAt(meshIndex);
if (!m)
throw eve::Exception("ModelData::getVertexCount: invalid mesh index");
return static_cast<int>(m->mNumVertices);
}
int ModelData::getFaceCount(int meshIndex) const {
const aiMesh *m = meshAt(meshIndex);
if (!m)
throw eve::Exception("ModelData::getFaceCount: invalid mesh index");
return static_cast<int>(m->mNumFaces);
}
bool ModelData::hasNormals(int meshIndex) const {
const aiMesh *m = meshAt(meshIndex);
if (!m)
throw eve::Exception("ModelData::hasNormals: invalid mesh index");
return m->HasNormals();
}
bool ModelData::hasTexCoords(int meshIndex) const {
const aiMesh *m = meshAt(meshIndex);
if (!m)
throw eve::Exception("ModelData::hasTexCoords: invalid mesh index");
return m->HasTextureCoords(0);
}
} // namespace model3d
} // namespace eve
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include "common/Resource.h"
#include "medialoader/model/ModelScene.h"
#include <string>
struct aiMesh;
struct aiScene;
namespace eve {
namespace model3d {
/**
* CPU-side decoded 3D model (Assimp scene owned via medialoader::ModelScene).
* Does not upload to GPU — use graphics::Graphics::newMeshFromAssimp on getMesh().
*/
class ModelData : public Resource {
public:
explicit ModelData(medialoader::ModelScene scene, std::string uri = "");
~ModelData() override;
bool empty() const;
int getMeshCount() const;
int getMaterialCount() const;
int getVertexCount(int meshIndex) const;
int getFaceCount(int meshIndex) const;
bool hasNormals(int meshIndex) const;
bool hasTexCoords(int meshIndex) const;
const aiScene *getScene() const;
const aiMesh *getMesh(int meshIndex) const;
private:
const aiMesh *meshAt(int meshIndex) const;
medialoader::ModelScene scene;
};
} // namespace model3d
} // namespace eve
+111
View File
@@ -0,0 +1,111 @@
#include "plugins/Plugins.h"
#include "common/Exception.h"
#include <simplesquirrel/simplesquirrel.hpp>
#if defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <dlfcn.h>
#endif
namespace eve::plugins {
using PluginInitFn = int (*)();
Module_IMPL(Plugins, new Plugins());
Plugins::Plugins() = default;
Plugins::~Plugins() {
for (auto& kv : loaded_) {
#if defined(_WIN32)
if (kv.second.native)
FreeLibrary(static_cast<HMODULE>(kv.second.native));
#else
if (kv.second.native)
dlclose(kv.second.native);
#endif
}
loaded_.clear();
}
bool Plugins::load(const std::string& path) {
if (path.empty())
throw Exception("plugins.load: empty path");
if (loaded_.count(path))
return true;
#if defined(_WIN32)
HMODULE mod = LoadLibraryA(path.c_str());
if (!mod)
throw Exception("plugins.load: LoadLibrary failed for '%s' (err=%lu)", path.c_str(),
GetLastError());
auto init = reinterpret_cast<PluginInitFn>(GetProcAddress(mod, "eve_plugin_init"));
if (!init) {
FreeLibrary(mod);
throw Exception("plugins.load: missing eve_plugin_init in '%s'", path.c_str());
}
int rc = init();
if (rc != 0) {
FreeLibrary(mod);
throw Exception("plugins.load: eve_plugin_init returned %d for '%s'", rc, path.c_str());
}
loaded_[path] = Handle{mod, path};
#else
void* mod = dlopen(path.c_str(), RTLD_NOW | RTLD_GLOBAL);
if (!mod)
throw Exception("plugins.load: dlopen failed for '%s': %s", path.c_str(), dlerror());
dlerror();
auto init = reinterpret_cast<PluginInitFn>(dlsym(mod, "eve_plugin_init"));
const char* err = dlerror();
if (err != nullptr || !init) {
dlclose(mod);
throw Exception("plugins.load: missing eve_plugin_init in '%s'%s%s", path.c_str(),
err ? ": " : "", err ? err : "");
}
int rc = init();
if (rc != 0) {
dlclose(mod);
throw Exception("plugins.load: eve_plugin_init returned %d for '%s'", rc, path.c_str());
}
loaded_[path] = Handle{mod, path};
#endif
if (ModuleManager::expose_pending() < 0)
throw Exception("plugins.load: expose_pending failed after loading '%s'", path.c_str());
return true;
}
bool Plugins::unload(const std::string& path) {
auto it = loaded_.find(path);
if (it == loaded_.end())
return false;
#if defined(_WIN32)
FreeLibrary(static_cast<HMODULE>(it->second.native));
#else
dlclose(it->second.native);
#endif
loaded_.erase(it);
return true;
}
bool Plugins::isLoaded(const std::string& path) const {
return loaded_.count(path) > 0;
}
void Plugins::expose(ssq::Table& table) {
auto cls = table.addClass(name, Plugins::create, false);
expose(cls);
}
void Plugins::expose(ssq::Class& cls) {
cls.addFunc("getName", &Plugins::getName);
cls.addFunc("load", &Plugins::load);
cls.addFunc("unload", &Plugins::unload);
cls.addFunc("isLoaded", &Plugins::isLoaded);
}
} // namespace eve::plugins
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "common/Module.h"
#include <string>
#include <unordered_map>
namespace eve::plugins {
// Loads native shared libraries (dll / so / dylib) that export eve_plugin_init.
class Plugins : public Module {
public:
Module_REG(Plugins);
Plugins();
~Plugins() override;
// Absolute or game-relative path. Returns true on success.
bool load(const std::string& path);
bool unload(const std::string& path);
bool isLoaded(const std::string& path) const;
private:
struct Handle {
void* native = nullptr;
std::string path;
};
std::unordered_map<std::string, Handle> loaded_;
};
} // namespace eve::plugins
+1
View File
@@ -44,6 +44,7 @@ mouse <- eve.Mouse();
touch <- eve.Touch();
sound <- eve.Sound();
audio <- eve.Audio();
model3d <- eve.Model3D();
eve_init <- function() {};
eve_update <- function(dt) {};
+5 -1
View File
@@ -22,8 +22,10 @@ file(GLOB all_test_cpp
UIPhaseAdv.cpp
UIOverlaySmoke.cpp
particles.cpp
shader.cpp
simplesquirrel.cpp
sound.cpp
model3d.cpp
audio.cpp
window.cpp
window_cpp.cpp
@@ -121,7 +123,7 @@ else()
endif()
endif()
set(EVELIBS EVCmdLine EVScripts EVWindow EVFileSystem EVMouse EVKeyboard EVJoystick EVTouch EVGraphics EVImage EVDevTools EVCommon EVEvent EVTimer EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles)
set(EVELIBS EVCmdLine EVScripts EVWindow EVFileSystem EVMouse EVKeyboard EVJoystick EVTouch EVGraphics EVImage EVDevTools EVCommon EVEvent EVTimer EVNetwork EVData EVSound EVAudio EVUI eve_imgui EVParticles EVModel3D)
if(EVENGINE_BUILD_DEMO)
list(APPEND EVELIBS EVDemo)
@@ -132,6 +134,8 @@ if(EVENGINE_PLATFORM_LIB)
endif()
target_link_libraries(unit_test ${EVELIBS} zeroerr ${ThirdParty} ${EVENGINE_VULKAN_LIB})
target_compile_definitions(unit_test PRIVATE
EVENGINE_TEST_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}")
include(${CMAKE_SOURCE_DIR}/cmake/ZeroErrDiscoverTests.cmake)
zeroerr_discover_tests(unit_test)
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+648
View File
@@ -0,0 +1,648 @@
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "model3d/Model3D.h"
#include "model3d/ModelData.h"
#include "data/ByteData.h"
#include "filesystem/Filesystem.h"
#include "common/Exception.h"
#include "graphics/Graphics.h"
#include "graphics/Mesh.h"
#include "graphics/RenderSystem.h"
#include "graphics/RenderSystem3D.h"
#include "image/Image.h"
#include "image/ImageData.h"
#include "window/Window.h"
#include <assimp/mesh.h>
#include <assimp/scene.h>
#include <assimp/material.h>
#include <assimp/texture.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "graphics/shaders/mesh3d_toon_frag_spv.inc"
#include "graphics/shaders/mesh3d_toon_vert_spv.inc"
using namespace eve::graphics;
namespace {
std::string pathBesideThisSource(const char *filename) {
std::string here = __FILE__;
auto slash = here.find_last_of("/\\");
std::string dir = (slash == std::string::npos) ? std::string(".") : here.substr(0, slash);
return dir + "/" + filename;
}
std::vector<char> readBinaryFile(const std::string &path) {
std::ifstream in(path, std::ios::binary);
if (!in)
return {};
return std::vector<char>((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
}
float luma(const Color &c) { return (c.r + c.g + c.b) / 3.f; }
void openGfxWindow(eve::window::Window *&win, Graphics *&gfx, int w = 640, int h = 480) {
win = eve::window::Window::create();
gfx = Graphics::create();
REQUIRE(win != nullptr);
REQUIRE(gfx != nullptr);
win->setGraphics(gfx);
eve::window::WindowSettings s;
s.width = w;
s.height = h;
s.centered = true;
REQUIRE(win->setWindowSettings(s));
}
void resetScene3D() {
if (ecs::ComponentManager<Renderable3D>::inst().registy != nullptr) {
auto view = ecs::View<Renderable3D, Renderable3D::MeshRenderer>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [mr] = *it;
mr->visible = false;
}
}
if (ecs::ComponentManager<Camera3D>::inst().registy != nullptr) {
auto camView = ecs::View<Camera3D, Camera3D::Data>();
for (auto it = camView.begin(); it != camView.end(); ++it) {
auto [data] = *it;
data->active = false;
}
}
if (ecs::ComponentManager<Renderable2D>::inst().registy != nullptr) {
auto view = ecs::View<Renderable2D, Renderable2D::Sprite>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [sp] = *it;
sp->visible = false;
}
}
}
Texture *makeSolidGray(Graphics *gfx, uint8_t g) {
uint8_t px[4] = {g, g, g, 255};
return gfx->newTexture(1, 1, px);
}
Texture *makeSolidRGB(Graphics *gfx, uint8_t r, uint8_t g, uint8_t b) {
uint8_t px[4] = {r, g, b, 255};
return gfx->newTexture(1, 1, px);
}
Texture *textureFromImageData(Graphics *gfx, eve::image::ImageData *img) {
if (!img)
return nullptr;
return gfx->newTexture(img);
}
Texture *loadAssimpDiffuseTexture(Graphics *gfx, const aiScene *scene, const aiMaterial *mat) {
if (!scene || !mat)
return nullptr;
aiString path;
if (mat->GetTexture(aiTextureType_DIFFUSE, 0, &path) != AI_SUCCESS)
return nullptr;
const char *p = path.C_Str();
if (!p || !p[0])
return nullptr;
eve::image::Image::create();
// Embedded: "*0", "*1", ...
if (p[0] == '*') {
int idx = std::atoi(p + 1);
if (idx < 0 || static_cast<unsigned>(idx) >= scene->mNumTextures)
return nullptr;
const aiTexture *tex = scene->mTextures[idx];
if (!tex || !tex->pcData)
return nullptr;
if (tex->mHeight == 0) {
// Compressed blob (png/jpg/...).
eve::data::ByteData bytes(tex->pcData, static_cast<size_t>(tex->mWidth));
try {
eve::image::ImageData *img = eve::image::Image::create()->newImageData(&bytes);
Texture *t = textureFromImageData(gfx, img);
delete img;
return t;
} catch (...) {
return nullptr;
}
}
// Uncompressed BGRA8888.
const unsigned w = tex->mWidth;
const unsigned h = tex->mHeight;
std::vector<uint8_t> rgba(size_t(w) * size_t(h) * 4);
const aiTexel *src = tex->pcData;
for (unsigned i = 0; i < w * h; ++i) {
rgba[i * 4 + 0] = src[i].r;
rgba[i * 4 + 1] = src[i].g;
rgba[i * 4 + 2] = src[i].b;
rgba[i * 4 + 3] = src[i].a;
}
return gfx->newTexture(int(w), int(h), rgba.data());
}
// External file — try VFS then OS path beside test assets.
try {
auto *fs = eve::filesystem::Filesystem::create();
std::unique_ptr<eve::filesystem::FileData> fd(fs->read(p));
if (fd && fd->getSize() > 0) {
eve::image::ImageData *img = eve::image::Image::create()->newImageData(fd.get());
Texture *t = textureFromImageData(gfx, img);
delete img;
return t;
}
} catch (...) {
}
// Basename / common relative layouts under test/
std::string base = p;
auto slash = base.find_last_of("/\\");
if (slash != std::string::npos)
base = base.substr(slash + 1);
const char *candidates[] = {
base.c_str(),
nullptr, // filled below for Textures/
};
std::string underTextures = std::string("Textures/") + base;
candidates[1] = underTextures.c_str();
for (const char *rel : candidates) {
if (!rel || !rel[0])
continue;
auto fileBytes = readBinaryFile(pathBesideThisSource(rel));
if (fileBytes.empty())
continue;
try {
eve::data::ByteData bytes(fileBytes.data(), fileBytes.size());
eve::image::ImageData *img = eve::image::Image::create()->newImageData(&bytes);
Texture *t = textureFromImageData(gfx, img);
delete img;
return t;
} catch (...) {
}
}
return nullptr;
}
Texture *loadTextureFile(Graphics *gfx, const char *relPathBesideTest) {
if (!relPathBesideTest || !relPathBesideTest[0])
return nullptr;
auto fileBytes = readBinaryFile(pathBesideThisSource(relPathBesideTest));
if (fileBytes.empty())
return nullptr;
eve::image::Image::create();
try {
eve::data::ByteData bytes(fileBytes.data(), fileBytes.size());
eve::image::ImageData *img = eve::image::Image::create()->newImageData(&bytes);
Texture *t = textureFromImageData(gfx, img);
delete img;
return t;
} catch (...) {
return nullptr;
}
}
struct Bounds {
float minX = 0, minY = 0, minZ = 0;
float maxX = 0, maxY = 0, maxZ = 0;
bool valid = false;
void expand(float x, float y, float z) {
if (!valid) {
minX = maxX = x;
minY = maxY = y;
minZ = maxZ = z;
valid = true;
return;
}
minX = std::min(minX, x);
minY = std::min(minY, y);
minZ = std::min(minZ, z);
maxX = std::max(maxX, x);
maxY = std::max(maxY, y);
maxZ = std::max(maxZ, z);
}
float centerX() const { return 0.5f * (minX + maxX); }
float centerY() const { return 0.5f * (minY + maxY); }
float centerZ() const { return 0.5f * (minZ + maxZ); }
float radius() const {
const float dx = maxX - minX;
const float dy = maxY - minY;
const float dz = maxZ - minZ;
return 0.5f * std::sqrt(dx * dx + dy * dy + dz * dz);
}
};
Bounds boundsOf(eve::model3d::ModelData *md) {
Bounds b;
for (int i = 0; i < md->getMeshCount(); ++i) {
const aiMesh *mesh = md->getMesh(i);
if (!mesh)
continue;
for (unsigned v = 0; v < mesh->mNumVertices; ++v)
b.expand(mesh->mVertices[v].x, mesh->mVertices[v].y, mesh->mVertices[v].z);
}
return b;
}
/** Upload every mesh with Assimp material tint/texture; frame camera; save PNG.
* @param fallbackDiffuse optional path relative to test/ when Assimp has no map.
* @param makeStyle optional factory: given Graphics*, return Mesh3D Shader for all entities. */
void renderModelSmoke(eve::model3d::ModelData *md, const char *pngName,
const char *fallbackDiffuse = nullptr,
std::function<Shader *(Graphics *)> makeStyle = {}) {
REQUIRE(md != nullptr);
REQUIRE(md->getMeshCount() >= 1);
REQUIRE(pngName != nullptr);
const aiScene *scene = md->getScene();
REQUIRE(scene != nullptr);
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx);
resetScene3D();
Shader *styleShader = makeStyle ? makeStyle(gfx) : nullptr;
Bounds b = boundsOf(md);
REQUIRE(b.valid);
const float cx = b.centerX();
const float cy = b.centerY();
const float cz = b.centerZ();
const float rad = std::max(0.5f, b.radius());
Texture *white = makeSolidGray(gfx, 255);
Texture *fallbackTex = loadTextureFile(gfx, fallbackDiffuse);
struct MatLook {
Texture *tex = nullptr;
float tr = 1.f, tg = 1.f, tb = 1.f;
};
std::unordered_map<unsigned, MatLook> matCache;
int spawned = 0;
for (int i = 0; i < md->getMeshCount(); ++i) {
const aiMesh *ai = md->getMesh(i);
if (!ai || ai->mNumFaces == 0)
continue;
Mesh *mesh = gfx->newMeshFromAssimp(*ai);
REQUIRE(mesh != nullptr);
REQUIRE_GT(mesh->indexCount, 0);
MatLook look{white, 1.f, 1.f, 1.f};
const unsigned matIndex = ai->mMaterialIndex;
auto cached = matCache.find(matIndex);
if (cached != matCache.end()) {
look = cached->second;
} else if (scene->mMaterials && matIndex < scene->mNumMaterials) {
const aiMaterial *mat = scene->mMaterials[matIndex];
aiColor3D kd(1.f, 1.f, 1.f);
mat->Get(AI_MATKEY_COLOR_DIFFUSE, kd);
Texture *fromMat = loadAssimpDiffuseTexture(gfx, scene, mat);
if (fromMat) {
look.tex = fromMat;
look.tr = kd.r;
look.tg = kd.g;
look.tb = kd.b;
} else if (fallbackTex) {
look.tex = fallbackTex;
look.tr = look.tg = look.tb = 1.f;
} else {
look.tex = makeSolidRGB(gfx, uint8_t(std::clamp(kd.r, 0.f, 1.f) * 255.f + 0.5f),
uint8_t(std::clamp(kd.g, 0.f, 1.f) * 255.f + 0.5f),
uint8_t(std::clamp(kd.b, 0.f, 1.f) * 255.f + 0.5f));
look.tr = look.tg = look.tb = 1.f;
}
matCache[matIndex] = look;
} else if (fallbackTex) {
look.tex = fallbackTex;
matCache[matIndex] = look;
}
auto *ent = Renderable3D::create();
ent->meshRenderer()->mesh = mesh;
ent->meshRenderer()->texture = look.tex;
ent->meshRenderer()->shader = styleShader;
ent->meshRenderer()->visible = true;
ent->setTint(look.tr, look.tg, look.tb, 1.f);
++spawned;
}
REQUIRE(spawned >= 1);
auto *cam = Camera3D::createCamera();
// 3/4 view: above + along +Z. Fit clip planes to asset scale (Rock1 is ~1e3 units).
cam->setTarget(cx, cy, cz);
cam->setEye(cx + rad * 0.9f, cy + rad * 0.55f, cz + rad * 1.8f);
cam->data()->nearZ = std::max(0.05f, rad * 0.01f);
cam->data()->farZ = std::max(100.f, rad * 20.f);
auto *hud = Renderable2D::create();
hud->transform()->x = 0;
hud->transform()->y = 0;
hud->sprite()->width = 2;
hud->sprite()->height = 2;
hud->sprite()->r = 0.f;
hud->sprite()->g = 0.f;
hud->sprite()->b = 0.f;
gfx->setScreenReadbackEnabled(true);
gfx->setBackgroundColor(Color(0.12f, 0.14f, 0.18f, 1.f));
// Light rays travel from camera toward the subject (matches RenderSystem3D lighting test).
const float lx = cx - (cx + rad * 0.9f);
const float ly = cy - (cy + rad * 0.55f);
const float lz = cz - (cz + rad * 1.8f);
RenderSystem3D::setDirectionalLight(lx, ly + rad * 1.2f, lz, 1.8f, 1.8f, 1.8f);
for (int i = 0; i < 10; ++i) {
if (ecs::ComponentManager<Renderable3D>::inst().registy != nullptr) {
auto view = ecs::View<Renderable3D, Renderable3D::Transform3D, Renderable3D::MeshRenderer>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [xf, mr] = *it;
if (!mr->visible)
continue;
// Gentle yaw so materials stay readable in the last saved frame.
xf->yaw = float(i) * 0.06f;
}
}
RenderSystem3D::render(*gfx);
RenderSystem::render(*gfx);
}
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
eve::image::Image::create();
eve::image::ImageData *frame = gfx->newImageData();
REQUIRE(frame != nullptr);
eve::filesystem::FileData *png =
frame->encode(medialoader::FormatHandler::ENCODED_PNG, pngName, false);
REQUIRE(png != nullptr);
REQUIRE(png->getSize() > 0);
// Under the CMake test binary dir (build/.../test/out), already gitignored via build*/.
const std::string outDir = std::string(EVENGINE_TEST_BINARY_DIR) + "/out";
std::error_code ec;
std::filesystem::create_directories(outDir, ec);
const std::string outPath = outDir + "/" + pngName;
{
std::ofstream out(outPath, std::ios::binary);
REQUIRE(out.good());
out.write(static_cast<const char *>(png->getData()),
static_cast<std::streamsize>(png->getSize()));
REQUIRE(out.good());
}
std::printf("model3d render saved: %s (materials=%u cached=%zu style=%s)\n", outPath.c_str(),
scene->mNumMaterials, matCache.size(), styleShader ? "yes" : "no");
delete png;
delete frame;
win->close();
}
} // namespace
// Minimal unit cube as Wavefront OBJ (same shape as medialoader model tests).
static const char kCubeObj[] =
"v -0.5 -0.5 -0.5\n"
"v 0.5 -0.5 -0.5\n"
"v 0.5 0.5 -0.5\n"
"v -0.5 0.5 -0.5\n"
"v -0.5 -0.5 0.5\n"
"v 0.5 -0.5 0.5\n"
"v 0.5 0.5 0.5\n"
"v -0.5 0.5 0.5\n"
"f 1 2 3\n"
"f 1 3 4\n"
"f 5 6 7\n"
"f 5 7 8\n"
"f 1 2 6\n"
"f 1 6 5\n"
"f 2 3 7\n"
"f 2 7 6\n"
"f 3 4 8\n"
"f 3 8 7\n"
"f 4 1 5\n"
"f 4 5 8\n";
TEST_CASE("model3d.newModelData.objFromMemory") {
auto *mod = eve::model3d::Model3D::create();
eve::data::ByteData data(kCubeObj, sizeof(kCubeObj) - 1);
auto *md = mod->newModelData(&data, ".obj");
REQUIRE(md != nullptr);
CHECK(!md->empty());
CHECK(md->getMeshCount() >= 1);
CHECK(md->getVertexCount(0) > 0);
CHECK(md->getFaceCount(0) > 0);
CHECK(md->getMesh(0) != nullptr);
CHECK(md->getScene() != nullptr);
delete md;
}
TEST_CASE("model3d.newModelData.emptyThrows") {
auto *mod = eve::model3d::Model3D::create();
bool threw = false;
try {
mod->newModelData(nullptr, ".obj");
} catch (const eve::Exception &) {
threw = true;
}
CHECK(threw);
}
TEST_CASE("model3d.newModelData.invalidMeshIndexThrows") {
auto *mod = eve::model3d::Model3D::create();
eve::data::ByteData data(kCubeObj, sizeof(kCubeObj) - 1);
auto *md = mod->newModelData(&data, ".obj");
REQUIRE(md != nullptr);
bool threw = false;
try {
md->getVertexCount(999);
} catch (const eve::Exception &) {
threw = true;
}
CHECK(threw);
delete md;
}
TEST_CASE("model3d.newModelDataFromFile.missingThrows") {
auto *mod = eve::model3d::Model3D::create();
bool threw = false;
try {
mod->newModelDataFromFile("definitely_missing_eve_model3d.obj");
} catch (const eve::Exception &) {
threw = true;
}
CHECK(threw);
}
TEST_CASE("model3d.Model3D.getName") {
auto *mod = eve::model3d::Model3D::create();
CHECK(mod->getName() == "Model3D");
}
TEST_CASE("model3d.newModelData.rock1Fbx") {
const std::string path = pathBesideThisSource("Rock1.fbx");
auto bytes = readBinaryFile(path);
REQUIRE(!bytes.empty());
auto *mod = eve::model3d::Model3D::create();
eve::data::ByteData data(bytes.data(), bytes.size());
auto *md = mod->newModelData(&data, ".fbx");
REQUIRE(md != nullptr);
CHECK(!md->empty());
CHECK(md->getMeshCount() >= 1);
CHECK(md->getScene() != nullptr);
CHECK(md->hasTexCoords(0));
int totalVerts = 0;
int totalFaces = 0;
for (int i = 0; i < md->getMeshCount(); ++i) {
CHECK(md->getMesh(i) != nullptr);
int verts = md->getVertexCount(i);
int faces = md->getFaceCount(i);
CHECK(verts > 0);
CHECK(faces > 0);
totalVerts += verts;
totalFaces += faces;
}
CHECK(totalVerts > 0);
CHECK(totalFaces > 0);
delete md;
}
TEST_CASE("model3d.newModelDataFromFile.sofaObjMtl") {
const std::string objPath = pathBesideThisSource("sofa.obj");
const std::string mtlPath = pathBesideThisSource("sofa.mtl");
REQUIRE(!readBinaryFile(objPath).empty());
REQUIRE(!readBinaryFile(mtlPath).empty());
std::string testDir = pathBesideThisSource("");
if (!testDir.empty() && (testDir.back() == '/' || testDir.back() == '\\'))
testDir.pop_back();
auto *fs = eve::filesystem::Filesystem::create();
REQUIRE(fs->setIdentity("ev_ut_model3d_sofa", true));
REQUIRE(fs->setupWriteDirectory());
fs->allowMountingForPath(testDir);
REQUIRE(fs->mount(testDir, "", false));
auto *mod = eve::model3d::Model3D::create();
auto *md = mod->newModelDataFromFile("sofa.obj");
REQUIRE(md != nullptr);
CHECK(!md->empty());
CHECK(md->getMeshCount() >= 1);
// Blender MTL lists 5 materials; Assimp may add a default.
CHECK(md->getMaterialCount() >= 5);
CHECK(md->getScene() != nullptr);
int totalVerts = 0;
int totalFaces = 0;
for (int i = 0; i < md->getMeshCount(); ++i) {
CHECK(md->getMesh(i) != nullptr);
int verts = md->getVertexCount(i);
int faces = md->getFaceCount(i);
CHECK(verts > 0);
CHECK(faces > 0);
totalVerts += verts;
totalFaces += faces;
}
CHECK(totalVerts > 0);
CHECK(totalFaces > 0);
delete md;
CHECK(fs->unmount(testDir));
}
TEST_CASE("model3d.render.rock1Fbx") {
const std::string path = pathBesideThisSource("Rock1.fbx");
auto bytes = readBinaryFile(path);
REQUIRE(!bytes.empty());
REQUIRE(!readBinaryFile(pathBesideThisSource("Textures/Rock1_Diffuse.png")).empty());
auto *mod = eve::model3d::Model3D::create();
eve::data::ByteData data(bytes.data(), bytes.size());
auto *md = mod->newModelData(&data, ".fbx");
REQUIRE(md != nullptr);
// FBX material has no embedded map path; bind companion diffuse next to the asset.
renderModelSmoke(md, "model3d_rock1.png", "Textures/Rock1_Diffuse.png");
delete md;
}
TEST_CASE("model3d.render.rock1ToonShader") {
const std::string path = pathBesideThisSource("Rock1.fbx");
auto bytes = readBinaryFile(path);
REQUIRE(!bytes.empty());
REQUIRE(!readBinaryFile(pathBesideThisSource("Textures/Rock1_Diffuse.png")).empty());
auto *mod = eve::model3d::Model3D::create();
eve::data::ByteData data(bytes.data(), bytes.size());
auto *md = mod->newModelData(&data, ".fbx");
REQUIRE(md != nullptr);
renderModelSmoke(md, "model3d_rock1_toon.png", "Textures/Rock1_Diffuse.png",
[](Graphics *gfx) -> Shader * {
std::vector<uint32_t> vert(mesh3d_toon_vert_spv,
mesh3d_toon_vert_spv + mesh3d_toon_vert_spv_count);
std::vector<uint32_t> frag(mesh3d_toon_frag_spv,
mesh3d_toon_frag_spv + mesh3d_toon_frag_spv_count);
Shader *toon = gfx->newMeshShaderFromSpv(vert, frag);
if (!toon || !toon->gpuHandle)
throw eve::Exception("failed to create toon mesh shader");
toon->declareFloat("bands");
toon->declareFloat("rimPower");
toon->declareFloat("rimStrength");
toon->declareFloat("posterize");
toon->sendFloat("bands", 4.f);
toon->sendFloat("rimPower", 2.8f);
toon->sendFloat("rimStrength", 0.55f);
toon->sendFloat("posterize", 6.f);
return toon;
});
delete md;
}
TEST_CASE("model3d.render.sofaObjMtl") {
const std::string objPath = pathBesideThisSource("sofa.obj");
const std::string mtlPath = pathBesideThisSource("sofa.mtl");
REQUIRE(!readBinaryFile(objPath).empty());
REQUIRE(!readBinaryFile(mtlPath).empty());
std::string testDir = pathBesideThisSource("");
if (!testDir.empty() && (testDir.back() == '/' || testDir.back() == '\\'))
testDir.pop_back();
auto *fs = eve::filesystem::Filesystem::create();
REQUIRE(fs->setIdentity("ev_ut_model3d_sofa_render", true));
REQUIRE(fs->setupWriteDirectory());
fs->allowMountingForPath(testDir);
REQUIRE(fs->mount(testDir, "", false));
auto *mod = eve::model3d::Model3D::create();
auto *md = mod->newModelDataFromFile("sofa.obj");
REQUIRE(md != nullptr);
renderModelSmoke(md, "model3d_sofa.png");
delete md;
CHECK(fs->unmount(testDir));
}
+147
View File
@@ -0,0 +1,147 @@
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include <SDL2/SDL.h>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <memory>
#include <stdexcept>
#include <vector>
#include "common/Exception.h"
#include "filesystem/Filesystem.h"
#include "graphics/Graphics.h"
#include "graphics/RenderSystem.h"
#include "graphics/Shader.h"
#include "graphics/shaders/custom2d_frag_spv.inc"
#include "window/Window.h"
using namespace eve::graphics;
TEST_CASE("graphics.Shader.declareAndSendUniforms") {
Shader sh;
CHECK_EQ(sh.declareFloat("time"), 0);
CHECK_EQ(sh.declareVec4("tint"), 1);
CHECK_EQ(sh.usedFloats(), 5);
CHECK(sh.hasUniform("time"));
CHECK(sh.hasUniform("tint"));
sh.sendFloat("time", 1.5f);
sh.sendVec4("tint", 0.2f, 0.4f, 0.6f, 1.f);
float t = 0.f;
REQUIRE_EQ(sh.getFromVar("time", &t, sizeof(t)), int(sizeof(t)));
CHECK(std::abs(t - 1.5f) < 1e-5f);
float tint[4] = {};
REQUIRE_EQ(sh.getFromVar("tint", tint, sizeof(tint)), int(sizeof(tint)));
CHECK(std::abs(tint[0] - 0.2f) < 1e-5f);
CHECK(std::abs(tint[3] - 1.f) < 1e-5f);
bool threw = false;
try {
sh.sendFloat("missing", 1.f);
} catch (const eve::Exception &) {
threw = true;
}
CHECK(threw);
threw = false;
try {
sh.declareFloat("time"); // same size OK
sh.declareVec2("time"); // different size → error
} catch (const eve::Exception &) {
threw = true;
}
CHECK(threw);
}
TEST_CASE("graphics.Shader.customSpvOnSprite") {
auto *win = eve::window::Window::create();
auto *gfx = Graphics::create();
REQUIRE(win != nullptr);
REQUIRE(gfx != nullptr);
win->setGraphics(gfx);
eve::window::WindowSettings s;
s.width = 320;
s.height = 240;
s.centered = true;
REQUIRE(win->setWindowSettings(s));
std::vector<uint32_t> frag(custom2d_frag_spv, custom2d_frag_spv + custom2d_frag_spv_count);
Shader *shader = gfx->newShaderFromSpv({}, frag);
REQUIRE(shader != nullptr);
REQUIRE(shader->gpuHandle != nullptr);
CHECK_EQ(shader->declareFloat("factor"), 0);
shader->sendFloat("factor", 0.5f);
// White 2x2 texture
const uint8_t px[16] = {255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255};
Texture *tex = gfx->newTexture(2, 2, px);
REQUIRE(tex != nullptr);
auto *ent = Renderable2D::create();
ent->transform()->x = 40;
ent->transform()->y = 40;
ent->sprite()->width = 80;
ent->sprite()->height = 80;
ent->sprite()->texture = tex;
ent->sprite()->shader = shader;
ent->sprite()->visible = true;
gfx->setScreenReadbackEnabled(true);
gfx->setBackgroundColor(Color(0.f, 0.f, 0.f, 1.f));
// Warm up a couple frames (swapchain acquire can soft-fail once).
for (int i = 0; i < 3; ++i) RenderSystem::render(*gfx);
Color c = gfx->getPixel(80, 80);
// factor 0.5 on white → ~0.5 RGB (allow tolerance for sRGB/swapchain format)
CHECK_GT(c.r, 0.15f);
CHECK_LT(c.r, 0.85f);
CHECK(std::abs(c.r - c.g) < 0.15f);
}
#if !defined(_WIN32)
TEST_CASE("graphics.Shader.newShaderGlslc") {
auto *win = eve::window::Window::create();
auto *gfx = Graphics::create();
REQUIRE(win != nullptr);
REQUIRE(gfx != nullptr);
win->setGraphics(gfx);
eve::window::WindowSettings s;
s.width = 160;
s.height = 120;
s.centered = true;
if (!win->setWindowSettings(s)) return;
const char *frag = R"(#version 450
layout(location = 0) in vec4 fragColor;
layout(location = 1) in vec2 fragUV;
layout(location = 0) out vec4 outColor;
layout(binding = 0) uniform sampler2D MainTex;
layout(push_constant) uniform Externals { float data[32]; } u;
void main() {
outColor = texture(MainTex, fragUV) * fragColor * u.data[0];
}
)";
try {
Shader *sh = gfx->newShader(frag);
REQUIRE(sh != nullptr);
sh->declareFloat("factor");
sh->sendFloat("factor", 1.f);
gfx->setShader(sh);
CHECK(gfx->getShader() == sh);
gfx->setShader();
CHECK(gfx->getShader() == nullptr);
} catch (const eve::Exception &e) {
// glslc missing on CI hosts is acceptable — mark as soft skip
const char *msg = e.what();
bool missing = msg && (std::strstr(msg, "glslc") != nullptr);
CHECK(missing);
}
}
#endif
+52
View File
@@ -0,0 +1,52 @@
# Blender MTL File: 'sofa01.blend'
# Material Count: 5
newmtl Material.002
Ns 225.000000
Ka 1.000000 1.000000 1.000000
Kd 0.390722 0.186268 0.428691
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2
newmtl Material.003
Ns 225.000000
Ka 1.000000 1.000000 1.000000
Kd 0.428691 0.132868 0.114435
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2
newmtl Material.004
Ns 225.000000
Ka 1.000000 1.000000 1.000000
Kd 0.428691 0.205878 0.063647
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2
newmtl m_sofa
Ns 9.000004
Ka 1.000000 1.000000 1.000000
Kd 0.661863 0.556516 0.421331
Ks 0.000000 0.000000 0.000000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 1
newmtl m_sofa_bottom
Ns 225.000000
Ka 1.000000 1.000000 1.000000
Kd 0.132863 0.112958 0.087189
Ks 0.500000 0.500000 0.500000
Ke 0.0 0.0 0.0
Ni 1.450000
d 1.000000
illum 2
+11058
View File
File diff suppressed because it is too large. Load diff