mirror of
https://github.com/ScrelliCopter/NeHe-SDL_GPU.git
synced 2025-06-19 21:49:17 +10:00
c: Add lessons 1-10
This commit is contained in:
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/build/
|
||||||
|
/xcode/
|
||||||
|
/build-*/
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
.swiftpm/
|
||||||
|
.build/
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
Package.resolved
|
||||||
|
|
||||||
|
/out/
|
||||||
|
/target/
|
||||||
|
Cargo.lock
|
||||||
|
|
||||||
|
.vs/
|
||||||
|
.vscode/
|
||||||
|
CMakeSettings.json
|
||||||
|
|
||||||
|
Thumbs.db
|
||||||
|
.DS_Store
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.metallibsym
|
||||||
8
CMakeLists.txt
Normal file
8
CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
cmake_minimum_required(VERSION "3.11" FATAL_ERROR)
|
||||||
|
project(NeHe-SDL_GPU LANGUAGES C)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
|
||||||
|
|
||||||
|
find_package(SDL3 REQUIRED CONFIG)
|
||||||
|
|
||||||
|
add_subdirectory(src/c)
|
||||||
38
cmake/modules/AddLesson.cmake
Normal file
38
cmake/modules/AddLesson.cmake
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
function (add_lesson target)
|
||||||
|
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "SOURCES;SHADERS;DATA")
|
||||||
|
|
||||||
|
add_executable(${target} MACOSX_BUNDLE WIN32
|
||||||
|
application.c application.h
|
||||||
|
nehe.c nehe.h
|
||||||
|
matrix.c matrix.h)
|
||||||
|
set_property(TARGET ${target} PROPERTY C_STANDARD 99)
|
||||||
|
|
||||||
|
target_compile_options(${target} PRIVATE
|
||||||
|
$<$<C_COMPILER_ID:Clang,AppleClang>:-Weverything -Wno-declaration-after-statement -Wno-padded -Wno-switch-enum -Wno-cast-qual>
|
||||||
|
$<$<C_COMPILER_ID:GNU>:-Wall -Wextra -pedantic>
|
||||||
|
$<$<C_COMPILER_ID:MSVC>:/W4>)
|
||||||
|
target_compile_definitions(${target} PRIVATE
|
||||||
|
$<$<C_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>)
|
||||||
|
target_link_libraries(${target} SDL3::SDL3)
|
||||||
|
|
||||||
|
target_sources(${target} PRIVATE ${arg_SOURCES})
|
||||||
|
|
||||||
|
foreach (shader IN LISTS arg_SHADERS)
|
||||||
|
if (shader MATCHES "\\.metal$")
|
||||||
|
set(path "${CMAKE_SOURCE_DIR}/src/shaders/${shader}")
|
||||||
|
else()
|
||||||
|
set(path "${CMAKE_SOURCE_DIR}/data/shaders/${shader}")
|
||||||
|
endif()
|
||||||
|
set_source_files_properties(${path} PROPERTIES
|
||||||
|
HEADER_FILE_ONLY ON
|
||||||
|
MACOSX_PACKAGE_LOCATION "Resources/Shaders")
|
||||||
|
target_sources(${target} PRIVATE "${path}")
|
||||||
|
endforeach()
|
||||||
|
foreach (resource IN LISTS arg_DATA)
|
||||||
|
set(path "${CMAKE_SOURCE_DIR}/data/${resource}")
|
||||||
|
set_source_files_properties(${path} PROPERTIES
|
||||||
|
MACOSX_PACKAGE_LOCATION "Resources/Data")
|
||||||
|
target_sources(${target} PRIVATE "${path}")
|
||||||
|
endforeach()
|
||||||
|
unset(path)
|
||||||
|
endfunction()
|
||||||
BIN
data/Crate.bmp
Normal file
BIN
data/Crate.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
BIN
data/Glass.bmp
Normal file
BIN
data/Glass.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
data/Mud.bmp
Normal file
BIN
data/Mud.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
BIN
data/NeHe.bmp
Normal file
BIN
data/NeHe.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
BIN
data/Star.bmp
Normal file
BIN
data/Star.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
160
data/World.txt
Normal file
160
data/World.txt
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
|
||||||
|
NUMPOLLIES 36
|
||||||
|
|
||||||
|
// Floor 1
|
||||||
|
-3.0 0.0 -3.0 0.0 6.0
|
||||||
|
-3.0 0.0 3.0 0.0 0.0
|
||||||
|
3.0 0.0 3.0 6.0 0.0
|
||||||
|
|
||||||
|
-3.0 0.0 -3.0 0.0 6.0
|
||||||
|
3.0 0.0 -3.0 6.0 6.0
|
||||||
|
3.0 0.0 3.0 6.0 0.0
|
||||||
|
|
||||||
|
// Ceiling 1
|
||||||
|
-3.0 1.0 -3.0 0.0 6.0
|
||||||
|
-3.0 1.0 3.0 0.0 0.0
|
||||||
|
3.0 1.0 3.0 6.0 0.0
|
||||||
|
-3.0 1.0 -3.0 0.0 6.0
|
||||||
|
3.0 1.0 -3.0 6.0 6.0
|
||||||
|
3.0 1.0 3.0 6.0 0.0
|
||||||
|
|
||||||
|
// A1
|
||||||
|
|
||||||
|
-2.0 1.0 -2.0 0.0 1.0
|
||||||
|
-2.0 0.0 -2.0 0.0 0.0
|
||||||
|
-0.5 0.0 -2.0 1.5 0.0
|
||||||
|
-2.0 1.0 -2.0 0.0 1.0
|
||||||
|
-0.5 1.0 -2.0 1.5 1.0
|
||||||
|
-0.5 0.0 -2.0 1.5 0.0
|
||||||
|
|
||||||
|
// A2
|
||||||
|
|
||||||
|
2.0 1.0 -2.0 2.0 1.0
|
||||||
|
2.0 0.0 -2.0 2.0 0.0
|
||||||
|
0.5 0.0 -2.0 0.5 0.0
|
||||||
|
2.0 1.0 -2.0 2.0 1.0
|
||||||
|
0.5 1.0 -2.0 0.5 1.0
|
||||||
|
0.5 0.0 -2.0 0.5 0.0
|
||||||
|
|
||||||
|
// B1
|
||||||
|
|
||||||
|
-2.0 1.0 2.0 2.0 1.0
|
||||||
|
-2.0 0.0 2.0 2.0 0.0
|
||||||
|
-0.5 0.0 2.0 0.5 0.0
|
||||||
|
-2.0 1.0 2.0 2.0 1.0
|
||||||
|
-0.5 1.0 2.0 0.5 1.0
|
||||||
|
-0.5 0.0 2.0 0.5 0.0
|
||||||
|
|
||||||
|
// B2
|
||||||
|
|
||||||
|
2.0 1.0 2.0 2.0 1.0
|
||||||
|
2.0 0.0 2.0 2.0 0.0
|
||||||
|
0.5 0.0 2.0 0.5 0.0
|
||||||
|
2.0 1.0 2.0 2.0 1.0
|
||||||
|
0.5 1.0 2.0 0.5 1.0
|
||||||
|
0.5 0.0 2.0 0.5 0.0
|
||||||
|
|
||||||
|
// C1
|
||||||
|
|
||||||
|
-2.0 1.0 -2.0 0.0 1.0
|
||||||
|
-2.0 0.0 -2.0 0.0 0.0
|
||||||
|
-2.0 0.0 -0.5 1.5 0.0
|
||||||
|
-2.0 1.0 -2.0 0.0 1.0
|
||||||
|
-2.0 1.0 -0.5 1.5 1.0
|
||||||
|
-2.0 0.0 -0.5 1.5 0.0
|
||||||
|
|
||||||
|
// C2
|
||||||
|
|
||||||
|
-2.0 1.0 2.0 2.0 1.0
|
||||||
|
-2.0 0.0 2.0 2.0 0.0
|
||||||
|
-2.0 0.0 0.5 0.5 0.0
|
||||||
|
-2.0 1.0 2.0 2.0 1.0
|
||||||
|
-2.0 1.0 0.5 0.5 1.0
|
||||||
|
-2.0 0.0 0.5 0.5 0.0
|
||||||
|
|
||||||
|
// D1
|
||||||
|
|
||||||
|
2.0 1.0 -2.0 0.0 1.0
|
||||||
|
2.0 0.0 -2.0 0.0 0.0
|
||||||
|
2.0 0.0 -0.5 1.5 0.0
|
||||||
|
2.0 1.0 -2.0 0.0 1.0
|
||||||
|
2.0 1.0 -0.5 1.5 1.0
|
||||||
|
2.0 0.0 -0.5 1.5 0.0
|
||||||
|
|
||||||
|
// D2
|
||||||
|
|
||||||
|
2.0 1.0 2.0 2.0 1.0
|
||||||
|
2.0 0.0 2.0 2.0 0.0
|
||||||
|
2.0 0.0 0.5 0.5 0.0
|
||||||
|
2.0 1.0 2.0 2.0 1.0
|
||||||
|
2.0 1.0 0.5 0.5 1.0
|
||||||
|
2.0 0.0 0.5 0.5 0.0
|
||||||
|
|
||||||
|
// Upper hallway - L
|
||||||
|
-0.5 1.0 -3.0 0.0 1.0
|
||||||
|
-0.5 0.0 -3.0 0.0 0.0
|
||||||
|
-0.5 0.0 -2.0 1.0 0.0
|
||||||
|
-0.5 1.0 -3.0 0.0 1.0
|
||||||
|
-0.5 1.0 -2.0 1.0 1.0
|
||||||
|
-0.5 0.0 -2.0 1.0 0.0
|
||||||
|
|
||||||
|
// Upper hallway - R
|
||||||
|
0.5 1.0 -3.0 0.0 1.0
|
||||||
|
0.5 0.0 -3.0 0.0 0.0
|
||||||
|
0.5 0.0 -2.0 1.0 0.0
|
||||||
|
0.5 1.0 -3.0 0.0 1.0
|
||||||
|
0.5 1.0 -2.0 1.0 1.0
|
||||||
|
0.5 0.0 -2.0 1.0 0.0
|
||||||
|
|
||||||
|
// Lower hallway - L
|
||||||
|
-0.5 1.0 3.0 0.0 1.0
|
||||||
|
-0.5 0.0 3.0 0.0 0.0
|
||||||
|
-0.5 0.0 2.0 1.0 0.0
|
||||||
|
-0.5 1.0 3.0 0.0 1.0
|
||||||
|
-0.5 1.0 2.0 1.0 1.0
|
||||||
|
-0.5 0.0 2.0 1.0 0.0
|
||||||
|
|
||||||
|
// Lower hallway - R
|
||||||
|
0.5 1.0 3.0 0.0 1.0
|
||||||
|
0.5 0.0 3.0 0.0 0.0
|
||||||
|
0.5 0.0 2.0 1.0 0.0
|
||||||
|
0.5 1.0 3.0 0.0 1.0
|
||||||
|
0.5 1.0 2.0 1.0 1.0
|
||||||
|
0.5 0.0 2.0 1.0 0.0
|
||||||
|
|
||||||
|
|
||||||
|
// Left hallway - Lw
|
||||||
|
|
||||||
|
-3.0 1.0 0.5 1.0 1.0
|
||||||
|
-3.0 0.0 0.5 1.0 0.0
|
||||||
|
-2.0 0.0 0.5 0.0 0.0
|
||||||
|
-3.0 1.0 0.5 1.0 1.0
|
||||||
|
-2.0 1.0 0.5 0.0 1.0
|
||||||
|
-2.0 0.0 0.5 0.0 0.0
|
||||||
|
|
||||||
|
// Left hallway - Hi
|
||||||
|
|
||||||
|
-3.0 1.0 -0.5 1.0 1.0
|
||||||
|
-3.0 0.0 -0.5 1.0 0.0
|
||||||
|
-2.0 0.0 -0.5 0.0 0.0
|
||||||
|
-3.0 1.0 -0.5 1.0 1.0
|
||||||
|
-2.0 1.0 -0.5 0.0 1.0
|
||||||
|
-2.0 0.0 -0.5 0.0 0.0
|
||||||
|
|
||||||
|
// Right hallway - Lw
|
||||||
|
|
||||||
|
3.0 1.0 0.5 1.0 1.0
|
||||||
|
3.0 0.0 0.5 1.0 0.0
|
||||||
|
2.0 0.0 0.5 0.0 0.0
|
||||||
|
3.0 1.0 0.5 1.0 1.0
|
||||||
|
2.0 1.0 0.5 0.0 1.0
|
||||||
|
2.0 0.0 0.5 0.0 0.0
|
||||||
|
|
||||||
|
// Right hallway - Hi
|
||||||
|
|
||||||
|
3.0 1.0 -0.5 1.0 1.0
|
||||||
|
3.0 0.0 -0.5 1.0 0.0
|
||||||
|
2.0 0.0 -0.5 0.0 0.0
|
||||||
|
3.0 1.0 -0.5 1.0 1.0
|
||||||
|
2.0 1.0 -0.5 0.0 1.0
|
||||||
|
2.0 0.0 -0.5 0.0 0.0
|
||||||
BIN
data/shaders/lesson2.metallib
Normal file
BIN
data/shaders/lesson2.metallib
Normal file
Binary file not shown.
BIN
data/shaders/lesson3.metallib
Normal file
BIN
data/shaders/lesson3.metallib
Normal file
Binary file not shown.
BIN
data/shaders/lesson6.metallib
Normal file
BIN
data/shaders/lesson6.metallib
Normal file
Binary file not shown.
BIN
data/shaders/lesson7.metallib
Normal file
BIN
data/shaders/lesson7.metallib
Normal file
Binary file not shown.
BIN
data/shaders/lesson9.metallib
Normal file
BIN
data/shaders/lesson9.metallib
Normal file
Binary file not shown.
186
scripts/compile_shaders.py
Executable file
186
scripts/compile_shaders.py
Executable file
@@ -0,0 +1,186 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
SPDX-License-Identifier: Zlib
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from collections import namedtuple
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
|
||||||
|
def compile_metal_shaders(
|
||||||
|
sources: list[str | Path], library: str | Path,
|
||||||
|
cflags: list[str] = None, sdk="macosx", debug: bool = False, cwd: Path | None = None) -> None:
|
||||||
|
"""Build a Metal shader library from a list of Metal shaders
|
||||||
|
|
||||||
|
:param sources: List of Metal shader source paths
|
||||||
|
:param library: Path to the Metal library to build
|
||||||
|
:param cflags: Optional list of additional compiler parameters
|
||||||
|
:param sdk: Name of the Xcode platform SDK to use (default: "macosx"),
|
||||||
|
can be "macosx", "iphoneos", "iphonesimulator", "appletvos", or "appletvsimulator"
|
||||||
|
:param debug: Generate a symbol file that can be used to debug the shader library
|
||||||
|
:param cwd: Optionally set the current working directory for the compiler
|
||||||
|
"""
|
||||||
|
if cflags is None:
|
||||||
|
cflags = []
|
||||||
|
|
||||||
|
def xcrun_find_program(name: str):
|
||||||
|
return subprocess.run(["xcrun", "-sdk", sdk, "-f", name],
|
||||||
|
capture_output=True, check=True).stdout.decode("utf-8").strip()
|
||||||
|
|
||||||
|
# Find Metal compilers
|
||||||
|
metal = xcrun_find_program("metal")
|
||||||
|
if debug:
|
||||||
|
metal_dsymutil = xcrun_find_program("metal-dsymutil")
|
||||||
|
else:
|
||||||
|
metallib = xcrun_find_program("metallib")
|
||||||
|
|
||||||
|
# Compile each source to an AIR (Apple Intermediate Representation)
|
||||||
|
cflags = cflags + ["-frecord-sources"]
|
||||||
|
air_objects = [f"{str(s).removesuffix('.metal')}.air" for s in sources]
|
||||||
|
for src, obj in zip(sources, air_objects):
|
||||||
|
subprocess.run([metal, *cflags, "-c", src, "-o", obj], cwd=cwd, check=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Build the Metal library
|
||||||
|
if debug:
|
||||||
|
subprocess.run([metal, "-frecord-sources", "-o", library, *air_objects], cwd=cwd, check=True)
|
||||||
|
subprocess.run([metal_dsymutil, "-flat", "-remove-source", library], cwd=cwd, check=True)
|
||||||
|
else:
|
||||||
|
subprocess.run([metallib, "-o", library, *air_objects], cwd=cwd, check=True)
|
||||||
|
finally:
|
||||||
|
# Clean up AIR objects
|
||||||
|
for obj in air_objects:
|
||||||
|
cwd.joinpath(obj).unlink()
|
||||||
|
|
||||||
|
|
||||||
|
Shader = namedtuple("Shader", ["source", "type", "output"])
|
||||||
|
|
||||||
|
|
||||||
|
def shaders_suffixes(shaders: list[Shader],
|
||||||
|
in_suffix: str | None, out_suffix: str | None) -> Iterable[Shader]:
|
||||||
|
"""Add file extensions to the source and outputs of a list of shaders
|
||||||
|
|
||||||
|
:param shaders: The list of Shader tuples
|
||||||
|
:param in_suffix: Optional file extension to append to source
|
||||||
|
:param out_suffix: Optional file extension to append to output
|
||||||
|
:return: Generator with the modified shaders
|
||||||
|
"""
|
||||||
|
for s in shaders:
|
||||||
|
yield Shader(
|
||||||
|
f"{s.source}.{in_suffix}" if in_suffix else s.source,
|
||||||
|
s.type,
|
||||||
|
f"{s.output}.{out_suffix}" if out_suffix else s.output)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_spirv_shaders(shaders: Iterable[Shader],
|
||||||
|
flags: list[str] = None, glslang: str | None=None, cwd: Path | None = None) -> None:
|
||||||
|
"""Compile shaders to SPIR-V using glslang
|
||||||
|
|
||||||
|
:param shaders: The list of shader source paths to compile
|
||||||
|
:param flags: List of additional flags to pass to glslang
|
||||||
|
:param glslang: Optional path to glslang executable, if `None` defaults to "glslang"
|
||||||
|
:param cwd: Optionally set the current working directory for the compiler
|
||||||
|
"""
|
||||||
|
if glslang is None:
|
||||||
|
glslang = "glslang"
|
||||||
|
if flags is None:
|
||||||
|
flags = []
|
||||||
|
|
||||||
|
for shader in shaders:
|
||||||
|
sflags = [*flags, "-V", "-S", shader.type, "-o", shader.output, shader.source]
|
||||||
|
subprocess.run([glslang, *sflags], cwd=cwd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_dxil_shaders(shaders: Iterable[Shader], dxc: str | None = None, cwd: Path | None = None) -> None:
|
||||||
|
"""Compile HLSL shaders to DXIL using DXC
|
||||||
|
|
||||||
|
:param shaders: The list of shader source paths to compile
|
||||||
|
:param dxc: Optional path to dxc excutable, if `None` defaults to "dxc"
|
||||||
|
:param cwd: Optionally set the current working directory for the compiler
|
||||||
|
"""
|
||||||
|
if dxc is None:
|
||||||
|
dxc = "dxc"
|
||||||
|
for shader in shaders:
|
||||||
|
entry, shader_type = {
|
||||||
|
"vert": ("VertexMain", "vs_6_0"),
|
||||||
|
"frag": ("FragmentMain", "ps_6_0") }[shader.type]
|
||||||
|
cflags = ["-E", entry, "-T", shader_type]
|
||||||
|
subprocess.run([dxc, *cflags, "-Fo", shader.output, shader.source], cwd=cwd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_dxbc_shaders(shaders: Iterable[Shader], cwd: Path | None = None) -> None:
|
||||||
|
"""Compile HLSL shaders to DXBC using FXC
|
||||||
|
|
||||||
|
:param shaders: The list of shader source paths to compile
|
||||||
|
:param cwd: Optionally set the current working directory for the compiler
|
||||||
|
"""
|
||||||
|
for shader in shaders:
|
||||||
|
entry, shader_type = {
|
||||||
|
"vert": ("VertexMain", "vs_5_1"),
|
||||||
|
"frag": ("FragmentMain", "ps_5_1") }[shader.type]
|
||||||
|
cflags = ["/E", entry, "/T", shader_type]
|
||||||
|
subprocess.run(["fxc", *cflags, "/Fo", shader.output, shader.source], cwd=cwd, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_shaders() -> None:
|
||||||
|
build_spirv = False
|
||||||
|
build_metal = True
|
||||||
|
build_dxil = False
|
||||||
|
build_dxbc = False
|
||||||
|
lessons = [ "lesson2", "lesson3", "lesson6", "lesson7", "lesson9" ]
|
||||||
|
src_dir = Path("src/shaders")
|
||||||
|
dest_dir = Path("data/shaders")
|
||||||
|
|
||||||
|
system = platform.system()
|
||||||
|
def add_shaders() -> Iterable[Shader]:
|
||||||
|
for lesson in lessons:
|
||||||
|
yield Shader(src_dir / f"{lesson}.vertex", "vert", dest_dir / f"{lesson}.vertex")
|
||||||
|
yield Shader(src_dir / f"{lesson}.fragment", "frag", dest_dir / f"{lesson}.fragment")
|
||||||
|
shaders = list(add_shaders())
|
||||||
|
|
||||||
|
root = Path(sys.argv[0]).resolve().parent.parent
|
||||||
|
root.joinpath(dest_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Try to find cross-platform shader compilers
|
||||||
|
glslang = shutil.which("glslang")
|
||||||
|
dxc = shutil.which("dxc", path=f"/opt/dxc/bin:{os.environ.get('PATH', os.defpath)}")
|
||||||
|
|
||||||
|
if build_spirv:
|
||||||
|
# Build SPIR-V shaders for Vulkan
|
||||||
|
compile_spirv_shaders(shaders_suffixes(shaders, "glsl", "spv"),
|
||||||
|
flags=["--quiet"], glslang=glslang, cwd=root)
|
||||||
|
|
||||||
|
if build_metal:
|
||||||
|
# Build Metal shaders on macOS
|
||||||
|
if system == "Darwin":
|
||||||
|
compile_platform = "macos"
|
||||||
|
sdk_platform = "macosx"
|
||||||
|
min_version = "10.11"
|
||||||
|
for lesson in lessons:
|
||||||
|
compile_metal_shaders(
|
||||||
|
sources=[src_dir / f"{lesson}.metal"],
|
||||||
|
library=dest_dir / f"{lesson}.metallib",
|
||||||
|
cflags=["-Wall", "-O3",
|
||||||
|
f"-std={compile_platform}-metal1.1",
|
||||||
|
f"-m{sdk_platform}-version-min={min_version}"],
|
||||||
|
sdk=sdk_platform,
|
||||||
|
cwd=root)
|
||||||
|
|
||||||
|
if build_dxil:
|
||||||
|
# Build HLSL shaders on Windows or when DXC is available
|
||||||
|
if system == "Windows" or dxc is not None:
|
||||||
|
compile_dxil_shaders(shaders_suffixes(shaders, "hlsl", "dxb"), dxc=dxc, cwd=root)
|
||||||
|
if build_dxbc:
|
||||||
|
if system == "Windows": # FXC is only available thru the Windows SDK
|
||||||
|
compile_dxbc_shaders(shaders_suffixes(shaders, "hlsl", "fxb"), cwd=root)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
compile_shaders()
|
||||||
12
src/c/CMakeLists.txt
Normal file
12
src/c/CMakeLists.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
include(AddLesson)
|
||||||
|
|
||||||
|
add_lesson(lesson1 SOURCES lesson1.c)
|
||||||
|
add_lesson(lesson2 SOURCES lesson2.c SHADERS lesson2.metallib)
|
||||||
|
add_lesson(lesson3 SOURCES lesson3.c SHADERS lesson3.metallib)
|
||||||
|
add_lesson(lesson4 SOURCES lesson4.c SHADERS lesson3.metallib)
|
||||||
|
add_lesson(lesson5 SOURCES lesson5.c SHADERS lesson3.metallib)
|
||||||
|
add_lesson(lesson6 SOURCES lesson6.c SHADERS lesson6.metallib DATA NeHe.bmp)
|
||||||
|
add_lesson(lesson7 SOURCES lesson7.c SHADERS lesson6.metallib lesson7.metallib DATA Crate.bmp)
|
||||||
|
add_lesson(lesson8 SOURCES lesson8.c SHADERS lesson6.metallib lesson7.metallib DATA Glass.bmp)
|
||||||
|
add_lesson(lesson9 SOURCES lesson9.c SHADERS lesson6.metallib lesson9.metallib DATA Star.bmp)
|
||||||
|
add_lesson(lesson10 SOURCES lesson10.c SHADERS lesson6.metallib DATA Mud.bmp World.txt)
|
||||||
211
src/c/application.c
Normal file
211
src/c/application.c
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
#define SDL_MAIN_USE_CALLBACKS
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
NeHeContext ctx;
|
||||||
|
bool fullscreen;
|
||||||
|
} AppState;
|
||||||
|
|
||||||
|
SDL_AppResult SDLCALL SDL_AppInit(void** appstate, int argc, char* argv[])
|
||||||
|
{
|
||||||
|
(void)argc; (void)argv;
|
||||||
|
|
||||||
|
// Initialise SDL
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO))
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init: %s", SDL_GetError());
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetCurrentThreadPriority(SDL_THREAD_PRIORITY_HIGH);
|
||||||
|
|
||||||
|
// Allocate application context
|
||||||
|
AppState* s = *appstate = SDL_malloc(sizeof(AppState));
|
||||||
|
if (!s)
|
||||||
|
{
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
*s = (AppState)
|
||||||
|
{
|
||||||
|
.ctx = (NeHeContext)
|
||||||
|
{
|
||||||
|
.window = NULL,
|
||||||
|
.device = NULL
|
||||||
|
},
|
||||||
|
.fullscreen = false
|
||||||
|
};
|
||||||
|
NeHeContext* ctx = &s->ctx;
|
||||||
|
ctx->baseDir = SDL_GetBasePath(); // Resources directory
|
||||||
|
|
||||||
|
// Initialise GPU context
|
||||||
|
if (!NeHe_InitGPU(ctx, appConfig.title, appConfig.width, appConfig.height))
|
||||||
|
{
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle depth buffer texture creation if requested
|
||||||
|
if (appConfig.createDepthFormat != SDL_GPU_TEXTUREFORMAT_INVALID)
|
||||||
|
{
|
||||||
|
unsigned backbufWidth, backbufHeight;
|
||||||
|
SDL_GetWindowSizeInPixels(ctx->window, (int*)&backbufWidth, (int*)&backbufHeight);
|
||||||
|
if (!NeHe_SetupDepthTexture(ctx, backbufWidth, backbufHeight, appConfig.createDepthFormat, 1.0f))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConfig.init)
|
||||||
|
{
|
||||||
|
if (!appConfig.init(ctx))
|
||||||
|
{
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AppResult SDLCALL SDL_AppIterate(void* appstate)
|
||||||
|
{
|
||||||
|
NeHeContext* ctx = &((AppState*)appstate)->ctx;
|
||||||
|
SDL_GPUCommandBuffer* cmdbuf = SDL_AcquireGPUCommandBuffer(ctx->device);
|
||||||
|
if (!cmdbuf)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AcquireGPUCommandBuffer: %s", SDL_GetError());
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUTexture* swapchainTex = NULL;
|
||||||
|
uint32_t swapchainWidth, swapchainHeight;
|
||||||
|
if (!SDL_WaitAndAcquireGPUSwapchainTexture(cmdbuf, ctx->window, &swapchainTex, &swapchainWidth, &swapchainHeight))
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_WaitAndAcquireGPUSwapchainTexture: %s", SDL_GetError());
|
||||||
|
SDL_CancelGPUCommandBuffer(cmdbuf);
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
if (!swapchainTex)
|
||||||
|
{
|
||||||
|
SDL_CancelGPUCommandBuffer(cmdbuf);
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConfig.createDepthFormat != SDL_GPU_TEXTUREFORMAT_INVALID && ctx->depthTexture
|
||||||
|
&& (ctx->depthTextureWidth != swapchainWidth || ctx->depthTextureHeight != swapchainHeight))
|
||||||
|
{
|
||||||
|
if (!NeHe_SetupDepthTexture(ctx, swapchainWidth, swapchainHeight, appConfig.createDepthFormat, 1.0f))
|
||||||
|
{
|
||||||
|
SDL_CancelGPUCommandBuffer(cmdbuf);
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appConfig.draw)
|
||||||
|
{
|
||||||
|
appConfig.draw(ctx, cmdbuf, swapchainTex);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmdbuf);
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_AppResult SDLCALL SDL_AppEvent(void* appstate, SDL_Event* event)
|
||||||
|
{
|
||||||
|
AppState* s = appstate;
|
||||||
|
|
||||||
|
switch (event->type)
|
||||||
|
{
|
||||||
|
case SDL_EVENT_QUIT:
|
||||||
|
return SDL_APP_SUCCESS;
|
||||||
|
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||||
|
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||||
|
s->fullscreen = event->type == SDL_EVENT_WINDOW_ENTER_FULLSCREEN;
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
case SDL_EVENT_KEY_DOWN:
|
||||||
|
if (event->key.key == SDLK_ESCAPE)
|
||||||
|
{
|
||||||
|
return SDL_APP_SUCCESS;
|
||||||
|
}
|
||||||
|
if (event->key.key == SDLK_F1)
|
||||||
|
{
|
||||||
|
SDL_SetWindowFullscreen(s->ctx.window, !s->fullscreen);
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
|
// Fallthrough
|
||||||
|
case SDL_EVENT_KEY_UP:
|
||||||
|
if (appConfig.key)
|
||||||
|
{
|
||||||
|
appConfig.key(&s->ctx, event->key.key, event->key.down, event->key.repeat);
|
||||||
|
}
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
if (appConfig.resize)
|
||||||
|
{
|
||||||
|
appConfig.resize(&s->ctx, event->window.data1, event->window.data2);
|
||||||
|
}
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
default:
|
||||||
|
return SDL_APP_CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLCALL SDL_AppQuit(void* appstate, SDL_AppResult result)
|
||||||
|
{
|
||||||
|
(void)result;
|
||||||
|
|
||||||
|
if (appstate)
|
||||||
|
{
|
||||||
|
NeHeContext* ctx = &((AppState*)appstate)->ctx;
|
||||||
|
if (appConfig.quit)
|
||||||
|
{
|
||||||
|
appConfig.quit(ctx);
|
||||||
|
}
|
||||||
|
if (appConfig.createDepthFormat != SDL_GPU_TEXTUREFORMAT_INVALID && ctx->depthTexture)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, ctx->depthTexture);
|
||||||
|
}
|
||||||
|
SDL_ReleaseWindowFromGPUDevice(ctx->device, ctx->window);
|
||||||
|
SDL_DestroyGPUDevice(ctx->device);
|
||||||
|
SDL_DestroyWindow(ctx->window);
|
||||||
|
SDL_free(ctx);
|
||||||
|
}
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef SDL_MAIN_USE_CALLBACKS
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
void* appstate = NULL;
|
||||||
|
SDL_AppResult res;
|
||||||
|
if ((res = SDL_AppInit(&appstate, argc, argv)) != SDL_APP_CONTINUE)
|
||||||
|
{
|
||||||
|
goto Quit;
|
||||||
|
}
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
if ((res = SDL_AppEvent(appstate, &event)) != SDL_APP_CONTINUE)
|
||||||
|
{
|
||||||
|
goto Quit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((res = SDL_AppIterate(appstate)) != SDL_APP_CONTINUE)
|
||||||
|
{
|
||||||
|
goto Quit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Quit:
|
||||||
|
SDL_AppQuit(appstate, res);
|
||||||
|
return res == SDL_APP_SUCCESS ? 0 : 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
22
src/c/application.h
Normal file
22
src/c/application.h
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#ifndef APPLICATION_H
|
||||||
|
#define APPLICATION_H
|
||||||
|
|
||||||
|
#include <SDL3/SDL_gpu.h>
|
||||||
|
#include <SDL3/SDL_keycode.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
struct NeHeContext;
|
||||||
|
|
||||||
|
extern const struct AppConfig
|
||||||
|
{
|
||||||
|
const char* const title;
|
||||||
|
int width, height;
|
||||||
|
SDL_GPUTextureFormat createDepthFormat;
|
||||||
|
bool (*init)(struct NeHeContext*);
|
||||||
|
void (*quit)(struct NeHeContext*);
|
||||||
|
void (*resize)(struct NeHeContext*, int width, int height);
|
||||||
|
void (*draw)(struct NeHeContext* restrict, SDL_GPUCommandBuffer* restrict, SDL_GPUTexture* restrict);
|
||||||
|
void (*key)(struct NeHeContext*, SDL_Keycode, bool down, bool repeat);
|
||||||
|
} appConfig;
|
||||||
|
|
||||||
|
#endif//APPLICATION_H
|
||||||
34
src/c/lesson1.c
Normal file
34
src/c/lesson1.c
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void Lesson1_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, NULL);
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's OpenGL Framework",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.init = NULL,
|
||||||
|
.quit = NULL,
|
||||||
|
.resize = NULL,
|
||||||
|
.draw = Lesson1_Draw
|
||||||
|
};
|
||||||
392
src/c/lesson10.c
Normal file
392
src/c/lesson10.c
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float u, v;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
Vertex vertices[3];
|
||||||
|
} Triangle;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int numTriangles;
|
||||||
|
Triangle* tris;
|
||||||
|
} Sector;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, z;
|
||||||
|
float yaw, pitch;
|
||||||
|
float walkBob, walkBobTheta;
|
||||||
|
} Camera;
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL, * psoBlend = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUTexture* texture = NULL;
|
||||||
|
static SDL_GPUSampler* samplers[3] = { NULL, NULL, NULL };
|
||||||
|
|
||||||
|
static bool blend = false;
|
||||||
|
static unsigned filter = 0;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
static Camera camera =
|
||||||
|
{
|
||||||
|
.x = 0.0f, .z = 0.0f,
|
||||||
|
.yaw = 0.0f, .pitch = 0.0f,
|
||||||
|
.walkBob = 0.0f, .walkBobTheta = 0.0f
|
||||||
|
};
|
||||||
|
static Sector world = { .numTriangles = 0, .tris = NULL };
|
||||||
|
|
||||||
|
|
||||||
|
static void ReadLine(SDL_IOStream* restrict file, char* restrict str, int max)
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
char* p = str;
|
||||||
|
for (int n = max - 1; n > 0; --n)
|
||||||
|
{
|
||||||
|
int8_t c;
|
||||||
|
if (!SDL_ReadS8(file, &c))
|
||||||
|
break;
|
||||||
|
(*p++) = c;
|
||||||
|
if (c == '\n')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(*p) = '\0';
|
||||||
|
} while (str[0] == '/' || str[0] == '\n' || str[0] == '\r');
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetupWorld(const NeHeContext* ctx)
|
||||||
|
{
|
||||||
|
SDL_IOStream* file = NeHe_OpenResource(ctx, "Data/World.txt", "r");
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to open \"%s\": %s", "Data/World.txt", SDL_GetError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int numTris;
|
||||||
|
char line[255];
|
||||||
|
ReadLine(file, line, sizeof(line));
|
||||||
|
SDL_sscanf(line, "NUMPOLLIES %d\n", &numTris);
|
||||||
|
|
||||||
|
world.tris = SDL_malloc(sizeof(Triangle) * (size_t)numTris);
|
||||||
|
world.numTriangles = numTris;
|
||||||
|
for (int tri = 0; tri < numTris; ++tri)
|
||||||
|
{
|
||||||
|
for (int vtx = 0; vtx < 3; ++vtx)
|
||||||
|
{
|
||||||
|
ReadLine(file, line, sizeof(line));
|
||||||
|
SDL_sscanf(line, "%f %f %f %f %f",
|
||||||
|
&world.tris[tri].vertices[vtx].x,
|
||||||
|
&world.tris[tri].vertices[vtx].y,
|
||||||
|
&world.tris[tri].vertices[vtx].z,
|
||||||
|
&world.tris[tri].vertices[vtx].u,
|
||||||
|
&world.tris[tri].vertices[vtx].v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson10_Init(NeHeContext* ctx)
|
||||||
|
{
|
||||||
|
SetupWorld(ctx);
|
||||||
|
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson6",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1, .fragmentSamplers = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
.offset = offsetof(Vertex, u)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
SDL_GPUGraphicsPipelineCreateInfo info =
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE // Right-handed coordinates
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.num_color_targets = 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup blend pipeline
|
||||||
|
const SDL_GPUTextureFormat swapchainTextureFormat = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window);
|
||||||
|
info.target_info.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = swapchainTextureFormat,
|
||||||
|
.blend_state =
|
||||||
|
{
|
||||||
|
.enable_blend = true,
|
||||||
|
.color_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||||
|
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||||
|
.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||||
|
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
|
||||||
|
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||||
|
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
psoBlend = SDL_CreateGPUGraphicsPipeline(ctx->device, &info);
|
||||||
|
if (!psoBlend)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup regular pipeline w/ depth testing
|
||||||
|
info.depth_stencil_state = (SDL_GPUDepthStencilState)
|
||||||
|
{
|
||||||
|
.compare_op = SDL_GPU_COMPAREOP_LESS, // Pass if pixel depth value tests less than the depth buffer value
|
||||||
|
.enable_depth_test = true, // Enable depth testing
|
||||||
|
.enable_depth_write = true
|
||||||
|
};
|
||||||
|
info.target_info.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = swapchainTextureFormat
|
||||||
|
};
|
||||||
|
info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||||
|
info.target_info.has_depth_stencil_target = true;
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &info);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((texture = NeHe_LoadTexture(ctx, "Data/Mud.bmp", true, true)) == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
samplers[0] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_NEAREST,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_NEAREST
|
||||||
|
});
|
||||||
|
samplers[1] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
});
|
||||||
|
samplers[2] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
|
||||||
|
.max_lod = FLT_MAX
|
||||||
|
});
|
||||||
|
if (!samplers[0] || !samplers[1] || !samplers[2])
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUSampler: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((vtxBuffer = NeHe_CreateVertexBuffer(ctx, world.tris, sizeof(Triangle) * (uint32_t)world.numTriangles)) == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson10_Quit(NeHeContext* ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
for (int i = SDL_arraysize(samplers) - 1; i > 0; --i)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUSampler(ctx->device, samplers[i]);
|
||||||
|
}
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, texture);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoBlend);
|
||||||
|
SDL_free(world.tris);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson10_Resize(NeHeContext* ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson10_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUDepthStencilTargetInfo depthInfo =
|
||||||
|
{
|
||||||
|
.texture = ctx->depthTexture,
|
||||||
|
.clear_depth = 1.0f, // Ensure depth buffer clears to furthest value
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
|
||||||
|
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.cycle = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, blend ? psoBlend : pso);
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &(SDL_GPUTextureSamplerBinding)
|
||||||
|
{
|
||||||
|
.texture = texture,
|
||||||
|
.sampler = samplers[filter]
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
// Bind world vertex buffer
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
// Setup the camera view matrix
|
||||||
|
float modelView[16];
|
||||||
|
Mtx_Rotation(modelView, camera.pitch, 1.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Rotate(modelView, 360.0f - camera.yaw, 0.0f, 1.0f, 0.0f);
|
||||||
|
Mtx_Translate(modelView, -camera.x, -(0.25f + camera.walkBob), -camera.z);
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
struct { float modelViewProj[16], color[4]; } u;
|
||||||
|
Mtx_Multiply(u.modelViewProj, projection, modelView);
|
||||||
|
SDL_memcpy(u.color, (float[4]){ 1.0f, 1.0f, 1.0f, 1.0f }, sizeof(float) * 4);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||||
|
|
||||||
|
// Draw world
|
||||||
|
SDL_DrawGPUPrimitives(pass, 3u * (uint32_t)world.numTriangles, 1, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
// Handle keyboard input
|
||||||
|
const bool *keys = SDL_GetKeyboardState(NULL);
|
||||||
|
|
||||||
|
const float piover180 = 0.0174532925f;
|
||||||
|
|
||||||
|
if (keys[SDL_SCANCODE_UP])
|
||||||
|
{
|
||||||
|
camera.x -= SDL_sinf(camera.yaw * piover180) * 0.05f;
|
||||||
|
camera.z -= SDL_cosf(camera.yaw * piover180) * 0.05f;
|
||||||
|
if (camera.walkBobTheta >= 359.0f)
|
||||||
|
{
|
||||||
|
camera.walkBobTheta = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
camera.walkBobTheta += 10.0f;
|
||||||
|
}
|
||||||
|
camera.walkBob = SDL_sinf(camera.walkBobTheta * piover180) / 20.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys[SDL_SCANCODE_DOWN])
|
||||||
|
{
|
||||||
|
camera.x += SDL_sinf(camera.yaw * piover180) * 0.05f;
|
||||||
|
camera.z += SDL_cosf(camera.yaw * piover180) * 0.05f;
|
||||||
|
if (camera.walkBobTheta <= 1.0f)
|
||||||
|
{
|
||||||
|
camera.walkBobTheta = 359.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
camera.walkBobTheta -= 10.0f;
|
||||||
|
}
|
||||||
|
camera.walkBob = SDL_sinf(camera.walkBobTheta * piover180) / 20.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys[SDL_SCANCODE_LEFT]) { camera.yaw += 1.0f; }
|
||||||
|
if (keys[SDL_SCANCODE_RIGHT]) { camera.yaw -= 1.0f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEUP]) { camera.pitch -= 1.0f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEDOWN]) { camera.pitch += 1.0f; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson10_Key(NeHeContext* ctx, SDL_Keycode key, bool down, bool repeat)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (down && !repeat)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case SDLK_B:
|
||||||
|
blend = !blend;
|
||||||
|
break;
|
||||||
|
case SDLK_F:
|
||||||
|
filter = (filter + 1) % SDL_arraysize(samplers);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "Lionel Brits & NeHe's 3D World Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
.init = Lesson10_Init,
|
||||||
|
.quit = Lesson10_Quit,
|
||||||
|
.resize = Lesson10_Resize,
|
||||||
|
.draw = Lesson10_Draw,
|
||||||
|
.key = Lesson10_Key
|
||||||
|
};
|
||||||
182
src/c/lesson2.c
Normal file
182
src/c/lesson2.c
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Triangle
|
||||||
|
{ 0.0f, 1.0f, 0.0f }, // Top
|
||||||
|
{ -1.0f, -1.0f, 0.0f }, // Bottom left
|
||||||
|
{ 1.0f, -1.0f, 0.0f }, // Bottom right
|
||||||
|
// Quad
|
||||||
|
{ -1.0f, 1.0f, 0.0f }, // Top left
|
||||||
|
{ 1.0f, 1.0f, 0.0f }, // Top right
|
||||||
|
{ 1.0f, -1.0f, 0.0f }, // Bottom right
|
||||||
|
{ -1.0f, -1.0f, 0.0f } // Bottom left
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson2_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson2",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window)
|
||||||
|
},
|
||||||
|
.num_color_targets = 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson2_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson2_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson2_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, NULL);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pso);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
float model[16], viewproj[16];
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
Mtx_Translation(model, -1.5f, 0.0f, -6.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Move to the right by 3 units and draw quad
|
||||||
|
Mtx_Translate(model, 3.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's First Polygon Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.init = Lesson2_Init,
|
||||||
|
.quit = Lesson2_Quit,
|
||||||
|
.resize = Lesson2_Resize,
|
||||||
|
.draw = Lesson2_Draw
|
||||||
|
};
|
||||||
189
src/c/lesson3.c
Normal file
189
src/c/lesson3.c
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float r, g, b, a;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Triangle
|
||||||
|
{ 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Top (red)
|
||||||
|
{ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Bottom left (green)
|
||||||
|
{ 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Bottom right (blue)
|
||||||
|
// Quad
|
||||||
|
{ -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f }, // Top left
|
||||||
|
{ 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f }, // Top right
|
||||||
|
{ 1.0f, -1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f }, // Bottom right
|
||||||
|
{ -1.0f, -1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f } // Bottom left
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson3_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson3",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
|
||||||
|
.offset = offsetof(Vertex, r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window)
|
||||||
|
},
|
||||||
|
.num_color_targets = 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson3_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson3_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson3_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, NULL);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pso);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
float model[16], viewproj[16];
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
Mtx_Translation(model, -1.5f, 0.0f, -6.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Move to the right by 3 units and draw quad
|
||||||
|
Mtx_Translate(model, 3.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's Color Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.init = Lesson3_Init,
|
||||||
|
.quit = Lesson3_Quit,
|
||||||
|
.resize = Lesson3_Resize,
|
||||||
|
.draw = Lesson3_Draw
|
||||||
|
};
|
||||||
197
src/c/lesson4.c
Normal file
197
src/c/lesson4.c
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float r, g, b, a;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Triangle
|
||||||
|
{ 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Top (red)
|
||||||
|
{ -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Bottom left (green)
|
||||||
|
{ 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Bottom right (blue)
|
||||||
|
// Quad
|
||||||
|
{ -1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f }, // Top left
|
||||||
|
{ 1.0f, 1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f }, // Top right
|
||||||
|
{ 1.0f, -1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f }, // Bottom right
|
||||||
|
{ -1.0f, -1.0f, 0.0f, 0.5f, 0.5f, 1.0f, 1.0f } // Bottom left
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
static float rotTri = 0.0f;
|
||||||
|
static float rotQuad = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson4_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson3",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
|
||||||
|
.offset = offsetof(Vertex, r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window)
|
||||||
|
},
|
||||||
|
.num_color_targets = 1,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson4_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson4_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson4_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, NULL);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pso);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
float model[16], viewproj[16];
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
Mtx_Translation(model, -1.5f, 0.0f, -6.0f);
|
||||||
|
Mtx_Rotate(model, rotTri, 0.0f, 1.0f, 0.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Draw quad 1.5 units to the right and 6 units into the camera
|
||||||
|
Mtx_Translation(model, 1.5f, 0.0f, -6.0f);
|
||||||
|
Mtx_Rotate(model, rotQuad, 1.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
rotTri += 0.2f;
|
||||||
|
rotQuad -= 0.15f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's Rotation Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.init = Lesson4_Init,
|
||||||
|
.quit = Lesson4_Quit,
|
||||||
|
.resize = Lesson4_Resize,
|
||||||
|
.draw = Lesson4_Draw
|
||||||
|
};
|
||||||
245
src/c/lesson5.c
Normal file
245
src/c/lesson5.c
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float r, g, b, a;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Pyramid
|
||||||
|
{ 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Top of pyramid (Red)
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Front-left of pyramid (Green)
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Front-right of pyramid (Blue)
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Back-right of pyramid (Green)
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Back-left of pyramid (Blue)
|
||||||
|
// Cube
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Top-right of top face (Green)
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Top-left of top face (Green)
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Bottom-left of top face (Green)
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f }, // Bottom-right of top face (Green)
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.0f, 1.0f }, // Top-right of bottom face (Orange)
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 1.0f, 0.5f, 0.0f, 1.0f }, // Top-left of bottom face (Orange)
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 1.0f, 0.5f, 0.0f, 1.0f }, // Bottom-left of bottom face (Orange)
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 1.0f, 0.5f, 0.0f, 1.0f }, // Bottom-right of bottom face (Orange)
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Top-right of front face (Red)
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Top-left of front face (Red)
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Bottom-left of front face (Red)
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f }, // Bottom-right of front face (Red)
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f }, // Top-right of back face (Yellow)
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f }, // Top-left of back face (Yellow)
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f }, // Bottom-left of back face (Yellow)
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f }, // Bottom-right of back face (Yellow)
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Top-right of left face (Blue)
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Top-left of left face (Blue)
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Bottom-left of left face (Blue)
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f }, // Bottom-right of left face (Blue)
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f }, // Top-right of right face (Violet)
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f }, // Top-left of right face (Violet)
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f }, // Bottom-left of right face (Violet)
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f } // Bottom-right of right face (Violet)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
// Pyramid
|
||||||
|
0, 1, 2, // Front
|
||||||
|
0, 2, 3, // Right
|
||||||
|
0, 3, 4, // Back
|
||||||
|
0, 4, 1, // Left
|
||||||
|
// Cube
|
||||||
|
5, 6, 7, 7, 8, 5, // Top
|
||||||
|
9, 10, 11, 11, 12, 9, // Bottom
|
||||||
|
13, 14, 15, 15, 16, 13, // Front
|
||||||
|
17, 18, 19, 19, 20, 17, // Back
|
||||||
|
21, 22, 23, 23, 24, 21, // Left
|
||||||
|
25, 26, 27, 27, 28, 25 // Right
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
static float rotTri = 0.0f;
|
||||||
|
static float rotQuad = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson5_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson3",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4,
|
||||||
|
.offset = offsetof(Vertex, r)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
},
|
||||||
|
.depth_stencil_state =
|
||||||
|
{
|
||||||
|
.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL,
|
||||||
|
.enable_depth_test = true,
|
||||||
|
.enable_depth_write = true
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window)
|
||||||
|
},
|
||||||
|
.num_color_targets = 1,
|
||||||
|
.depth_stencil_format = appConfig.createDepthFormat,
|
||||||
|
.has_depth_stencil_target = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson5_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson5_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson5_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUDepthStencilTargetInfo depthInfo =
|
||||||
|
{
|
||||||
|
.texture = ctx->depthTexture,
|
||||||
|
.clear_depth = 1.0f, // Ensure depth buffer clears to furthest value
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
|
||||||
|
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.cycle = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pso);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
float model[16], viewproj[16];
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
Mtx_Translation(model, -1.5f, 0.0f, -6.0f);
|
||||||
|
Mtx_Rotate(model, rotTri, 0.0f, 1.0f, 0.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 12, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Draw quad 1.5 units to the right and 7 units into the camera
|
||||||
|
Mtx_Translation(model, 1.5f, 0.0f, -7.0f);
|
||||||
|
Mtx_Rotate(model, rotQuad, 1.0f, 1.0f, 1.0f);
|
||||||
|
Mtx_Multiply(viewproj, projection, model);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj, sizeof(viewproj));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 36, 1, 12, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
rotTri += 0.2f;
|
||||||
|
rotQuad -= 0.15f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's Solid Object Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
.init = Lesson5_Init,
|
||||||
|
.quit = Lesson5_Quit,
|
||||||
|
.resize = Lesson5_Resize,
|
||||||
|
.draw = Lesson5_Draw
|
||||||
|
};
|
||||||
266
src/c/lesson6.c
Normal file
266
src/c/lesson6.c
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float u, v;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Front Face
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 1.0f },
|
||||||
|
// Back Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 1.0f, 0.0f },
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, 0.0f },
|
||||||
|
// Top Face
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 1.0f },
|
||||||
|
// Bottom Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, 0.0f },
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 1.0f, 0.0f },
|
||||||
|
// Right face
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, 0.0f },
|
||||||
|
// Left Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f },
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 1.0f, 0.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
0, 1, 2, 2, 3, 0, // Front
|
||||||
|
4, 5, 6, 6, 7, 4, // Back
|
||||||
|
8, 9, 10, 10, 11, 8, // Top
|
||||||
|
12, 13, 14, 14, 15, 12, // Bottom
|
||||||
|
16, 17, 18, 18, 19, 16, // Right
|
||||||
|
20, 21, 22, 22, 23, 20 // Left
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
static SDL_GPUSampler* sampler = NULL;
|
||||||
|
static SDL_GPUTexture* texture = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
static float xRot = 0.0f, yRot = 0.0f, zRot = 0.0f;
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson6_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson6",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1, .fragmentSamplers = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
.offset = offsetof(Vertex, u)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
},
|
||||||
|
.depth_stencil_state =
|
||||||
|
{
|
||||||
|
.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL,
|
||||||
|
.enable_depth_test = true,
|
||||||
|
.enable_depth_write = true
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window)
|
||||||
|
},
|
||||||
|
.num_color_targets = 1,
|
||||||
|
.depth_stencil_format = appConfig.createDepthFormat,
|
||||||
|
.has_depth_stencil_target = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((texture = NeHe_LoadTexture(ctx, "Data/NeHe.bmp", true, false)) == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
});
|
||||||
|
if (!sampler)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUSampler: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson6_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
SDL_ReleaseGPUSampler(ctx->device, sampler);
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, texture);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson6_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson6_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUDepthStencilTargetInfo depthInfo =
|
||||||
|
{
|
||||||
|
.texture = ctx->depthTexture,
|
||||||
|
.clear_depth = 1.0f, // Ensure depth buffer clears to furthest value
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
|
||||||
|
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.cycle = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pso);
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &(const SDL_GPUTextureSamplerBinding)
|
||||||
|
{
|
||||||
|
.texture = texture,
|
||||||
|
.sampler = sampler
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
float model[16];
|
||||||
|
struct { float modelViewProj[16], color[4]; } u;
|
||||||
|
|
||||||
|
// Move cube 5 units into the screen and apply some rotations
|
||||||
|
Mtx_Translation(model, 0.0f, 0.0f, -5.0f);
|
||||||
|
Mtx_Rotate(model, xRot, 1.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Rotate(model, yRot, 0.0f, 1.0f, 0.0f);
|
||||||
|
Mtx_Rotate(model, zRot, 0.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
Mtx_Multiply(u.modelViewProj, projection, model);
|
||||||
|
SDL_memcpy(u.color, (float[4]){ 1.0f, 1.0f, 1.0f, 1.0f }, sizeof(float) * 4);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||||
|
|
||||||
|
// Draw textured cube
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, SDL_arraysize(indices), 1, 0, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
xRot += 0.3f;
|
||||||
|
yRot += 0.2f;
|
||||||
|
zRot += 0.4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's Texture Mapping Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
.init = Lesson6_Init,
|
||||||
|
.quit = Lesson6_Quit,
|
||||||
|
.resize = Lesson6_Resize,
|
||||||
|
.draw = Lesson6_Draw
|
||||||
|
};
|
||||||
377
src/c/lesson7.c
Normal file
377
src/c/lesson7.c
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float nx, ny, nz;
|
||||||
|
float u, v;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Front Face
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f },
|
||||||
|
// Back Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f },
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f },
|
||||||
|
// Top Face
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
// Bottom Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
// Right face
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
// Left Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
0, 1, 2, 2, 3, 0, // Front
|
||||||
|
4, 5, 6, 6, 7, 4, // Back
|
||||||
|
8, 9, 10, 10, 11, 8, // Top
|
||||||
|
12, 13, 14, 14, 15, 12, // Bottom
|
||||||
|
16, 17, 18, 18, 19, 16, // Right
|
||||||
|
20, 21, 22, 22, 23, 20 // Left
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* psoUnlit = NULL, * psoLight = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
static SDL_GPUSampler* samplers[3] = { NULL, NULL, NULL };
|
||||||
|
static SDL_GPUTexture* texture = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
static bool lighting = false;
|
||||||
|
struct Light { float ambient[4], diffuse[4], position[4]; } static light =
|
||||||
|
{
|
||||||
|
.ambient = { 0.5f, 0.5f, 0.5f, 1.0f },
|
||||||
|
.diffuse = { 1.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
.position = { 0.0f, 0.0f, 2.0f, 1.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
static int filter = 0;
|
||||||
|
|
||||||
|
static float xRot = 0.0f, yRot = 0.0f;
|
||||||
|
static float xSpeed = 0.0f, ySpeed = 0.0f;
|
||||||
|
static float z = -5.0f;
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson7_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShaderUnlit, * fragmentShaderUnlit;
|
||||||
|
SDL_GPUShader* vertexShaderLight, * fragmentShaderLight;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShaderUnlit, &fragmentShaderUnlit, "lesson6",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1, .fragmentSamplers = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShaderLight, &fragmentShaderLight, "lesson7",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 2, .fragmentSamplers = 1 }))
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderUnlit);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderUnlit);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
.offset = offsetof(Vertex, u)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 2,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, nx)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUVertexInputState vertexInput =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPURasterizerState rasterizer =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUDepthStencilState depthStencil =
|
||||||
|
{
|
||||||
|
.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL,
|
||||||
|
.enable_depth_test = true,
|
||||||
|
.enable_depth_write = true
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUGraphicsPipelineTargetInfo targetInfo =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window)
|
||||||
|
},
|
||||||
|
.num_color_targets = 1,
|
||||||
|
.depth_stencil_format = appConfig.createDepthFormat,
|
||||||
|
.has_depth_stencil_target = true
|
||||||
|
};
|
||||||
|
|
||||||
|
psoUnlit = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShaderUnlit,
|
||||||
|
.fragment_shader = fragmentShaderUnlit,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state = vertexInput,
|
||||||
|
.rasterizer_state = rasterizer,
|
||||||
|
.depth_stencil_state = depthStencil,
|
||||||
|
.target_info = targetInfo
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderUnlit);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderUnlit);
|
||||||
|
if (!psoUnlit)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderLight);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderLight);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
psoLight = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShaderLight,
|
||||||
|
.fragment_shader = fragmentShaderLight,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state = vertexInput,
|
||||||
|
.rasterizer_state = rasterizer,
|
||||||
|
.depth_stencil_state = depthStencil,
|
||||||
|
.target_info = targetInfo
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderLight);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderLight);
|
||||||
|
if (!psoUnlit)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((texture = NeHe_LoadTexture(ctx, "Data/Crate.bmp", true, true)) == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
samplers[0] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_NEAREST,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_NEAREST
|
||||||
|
});
|
||||||
|
samplers[1] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
});
|
||||||
|
samplers[2] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
|
||||||
|
.max_lod = FLT_MAX
|
||||||
|
});
|
||||||
|
if (!samplers[0] || !samplers[1] || !samplers[2])
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUSampler: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson7_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
for (int i = SDL_arraysize(samplers) - 1; i > 0; --i)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUSampler(ctx->device, samplers[i]);
|
||||||
|
}
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, texture);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoLight);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoUnlit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson7_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson7_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUDepthStencilTargetInfo depthInfo =
|
||||||
|
{
|
||||||
|
.texture = ctx->depthTexture,
|
||||||
|
.clear_depth = 1.0f, // Ensure depth buffer clears to furthest value
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
|
||||||
|
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.cycle = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, lighting ? psoLight : psoUnlit);
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &(const SDL_GPUTextureSamplerBinding)
|
||||||
|
{
|
||||||
|
.texture = texture,
|
||||||
|
.sampler = samplers[filter]
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
// Setup the cube's model matrix
|
||||||
|
float model[16];
|
||||||
|
Mtx_Translation(model, 0.0f, 0.0f, z);
|
||||||
|
Mtx_Rotate(model, xRot, 1.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Rotate(model, yRot, 0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
if (lighting)
|
||||||
|
{
|
||||||
|
struct { float model[16], projection[16]; } u;
|
||||||
|
SDL_memcpy(u.model, model, sizeof(u.model));
|
||||||
|
SDL_memcpy(u.projection, projection, sizeof(u.projection));
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 1, &light, sizeof(light));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct { float modelViewProj[16], color[4]; } u;
|
||||||
|
Mtx_Multiply(u.modelViewProj, projection, model);
|
||||||
|
SDL_memcpy(u.color, (const float[4]){ 1.0f, 1.0f, 1.0f, 1.0f }, sizeof(float) * 4);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw textured cube
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, SDL_arraysize(indices), 1, 0, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
const bool* keys = SDL_GetKeyboardState(NULL);
|
||||||
|
|
||||||
|
if (keys[SDL_SCANCODE_PAGEUP]) { z -= 0.02f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEDOWN]) { z += 0.02f; }
|
||||||
|
if (keys[SDL_SCANCODE_UP]) { xSpeed -= 0.01f; }
|
||||||
|
if (keys[SDL_SCANCODE_DOWN]) { xSpeed += 0.01f; }
|
||||||
|
if (keys[SDL_SCANCODE_RIGHT]) { ySpeed += 0.1f; }
|
||||||
|
if (keys[SDL_SCANCODE_LEFT]) { ySpeed -= 0.1f; }
|
||||||
|
|
||||||
|
xRot += xSpeed;
|
||||||
|
yRot += ySpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson7_Key(NeHeContext* ctx, SDL_Keycode key, bool down, bool repeat)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (down && !repeat)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case SDLK_L:
|
||||||
|
lighting = !lighting;
|
||||||
|
break;
|
||||||
|
case SDLK_F:
|
||||||
|
filter = (filter + 1) % (int)SDL_arraysize(samplers);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's Textures, Lighting & Keyboard Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
.init = Lesson7_Init,
|
||||||
|
.quit = Lesson7_Quit,
|
||||||
|
.resize = Lesson7_Resize,
|
||||||
|
.draw = Lesson7_Draw,
|
||||||
|
.key = Lesson7_Key
|
||||||
|
};
|
||||||
400
src/c/lesson8.c
Normal file
400
src/c/lesson8.c
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
#include <float.h>
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float nx, ny, nz;
|
||||||
|
float u, v;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
// Front Face
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f },
|
||||||
|
// Back Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f },
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f },
|
||||||
|
// Top Face
|
||||||
|
{ -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
// Bottom Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
// Right face
|
||||||
|
{ 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f },
|
||||||
|
{ 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
// Left Face
|
||||||
|
{ -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
0, 1, 2, 2, 3, 0, // Front
|
||||||
|
4, 5, 6, 6, 7, 4, // Back
|
||||||
|
8, 9, 10, 10, 11, 8, // Top
|
||||||
|
12, 13, 14, 14, 15, 12, // Bottom
|
||||||
|
16, 17, 18, 18, 19, 16, // Right
|
||||||
|
20, 21, 22, 22, 23, 20 // Left
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* psoUnlit = NULL, * psoLight = NULL;
|
||||||
|
static SDL_GPUGraphicsPipeline* psoBlendUnlit = NULL, * psoBlendLight = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
static SDL_GPUSampler* samplers[3] = { NULL, NULL, NULL };
|
||||||
|
static SDL_GPUTexture* texture = NULL;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
static bool lighting = false;
|
||||||
|
static bool blending = false;
|
||||||
|
struct Light { float ambient[4], diffuse[4], position[4]; } static light =
|
||||||
|
{
|
||||||
|
.ambient = { 0.5f, 0.5f, 0.5f, 1.0f },
|
||||||
|
.diffuse = { 1.0f, 1.0f, 1.0f, 1.0f },
|
||||||
|
.position = { 0.0f, 0.0f, 2.0f, 1.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
static int filter = 0;
|
||||||
|
|
||||||
|
static float xRot = 0.0f, yRot = 0.0f;
|
||||||
|
static float xSpeed = 0.0f, ySpeed = 0.0f;
|
||||||
|
static float z = -5.0f;
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson8_Init(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShaderUnlit, * fragmentShaderUnlit;
|
||||||
|
SDL_GPUShader* vertexShaderLight, * fragmentShaderLight;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShaderUnlit, &fragmentShaderUnlit, "lesson6",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1, .fragmentSamplers = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShaderLight, &fragmentShaderLight, "lesson7",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 2, .fragmentSamplers = 1 }))
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderUnlit);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderUnlit);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
.offset = offsetof(Vertex, u)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 2,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, nx)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_GPUGraphicsPipelineCreateInfo psoInfo;
|
||||||
|
SDL_zero(psoInfo);
|
||||||
|
|
||||||
|
psoInfo.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||||
|
psoInfo.vertex_input_state = (SDL_GPUVertexInputState)
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
};
|
||||||
|
|
||||||
|
psoInfo.rasterizer_state = (SDL_GPURasterizerState)
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
};
|
||||||
|
|
||||||
|
psoInfo.target_info.num_color_targets = 1;
|
||||||
|
psoInfo.target_info.depth_stencil_format = appConfig.createDepthFormat;
|
||||||
|
psoInfo.target_info.has_depth_stencil_target = true;
|
||||||
|
|
||||||
|
// Common pipeline depth & colour target options
|
||||||
|
psoInfo.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
|
||||||
|
const SDL_GPUTextureFormat swapchainTextureFormat = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window);
|
||||||
|
|
||||||
|
// Setup depth/stencil & colour pipeline state for no blending
|
||||||
|
psoInfo.depth_stencil_state.enable_depth_test = true;
|
||||||
|
psoInfo.depth_stencil_state.enable_depth_write = true;
|
||||||
|
psoInfo.target_info.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = swapchainTextureFormat
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create unlit pipeline
|
||||||
|
psoInfo.vertex_shader = vertexShaderUnlit;
|
||||||
|
psoInfo.fragment_shader = fragmentShaderUnlit;
|
||||||
|
psoUnlit = SDL_CreateGPUGraphicsPipeline(ctx->device, &psoInfo);
|
||||||
|
|
||||||
|
// Create lit pipeline
|
||||||
|
psoInfo.vertex_shader = vertexShaderLight;
|
||||||
|
psoInfo.fragment_shader = fragmentShaderLight;
|
||||||
|
psoLight = SDL_CreateGPUGraphicsPipeline(ctx->device, &psoInfo);
|
||||||
|
|
||||||
|
// Setup depth/stencil & colour pipeline state for blending
|
||||||
|
psoInfo.depth_stencil_state.enable_depth_test = false;
|
||||||
|
psoInfo.depth_stencil_state.enable_depth_write = false;
|
||||||
|
psoInfo.target_info.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = swapchainTextureFormat,
|
||||||
|
.blend_state =
|
||||||
|
{
|
||||||
|
.enable_blend = true,
|
||||||
|
.color_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||||
|
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||||
|
.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||||
|
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
|
||||||
|
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||||
|
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create unlit blended pipeline
|
||||||
|
psoInfo.vertex_shader = vertexShaderUnlit;
|
||||||
|
psoInfo.fragment_shader = fragmentShaderUnlit;
|
||||||
|
psoBlendUnlit = SDL_CreateGPUGraphicsPipeline(ctx->device, &psoInfo);
|
||||||
|
|
||||||
|
// Create lit blended pipeline
|
||||||
|
psoInfo.vertex_shader = vertexShaderLight;
|
||||||
|
psoInfo.fragment_shader = fragmentShaderLight;
|
||||||
|
psoBlendLight = SDL_CreateGPUGraphicsPipeline(ctx->device, &psoInfo);
|
||||||
|
|
||||||
|
// Free shaders
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderLight);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderLight);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShaderUnlit);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShaderUnlit);
|
||||||
|
|
||||||
|
if (!psoUnlit || !psoLight || !psoBlendUnlit || !psoBlendLight)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((texture = NeHe_LoadTexture(ctx, "Data/Glass.bmp", true, true)) == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
samplers[0] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_NEAREST,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_NEAREST
|
||||||
|
});
|
||||||
|
samplers[1] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
});
|
||||||
|
samplers[2] = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST,
|
||||||
|
.max_lod = FLT_MAX
|
||||||
|
});
|
||||||
|
if (!samplers[0] || !samplers[1] || !samplers[2])
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUSampler: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson8_Quit(NeHeContext* restrict ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
for (int i = SDL_arraysize(samplers) - 1; i > 0; --i)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUSampler(ctx->device, samplers[i]);
|
||||||
|
}
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, texture);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoBlendLight);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoBlendUnlit);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoLight);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, psoUnlit);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson8_Resize(NeHeContext* restrict ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson8_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
const SDL_GPUDepthStencilTargetInfo depthInfo =
|
||||||
|
{
|
||||||
|
.texture = ctx->depthTexture,
|
||||||
|
.clear_depth = 1.0f, // Ensure depth buffer clears to furthest value
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE,
|
||||||
|
.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE,
|
||||||
|
.cycle = true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, &depthInfo);
|
||||||
|
SDL_GPUGraphicsPipeline* const pipelines[] = { psoUnlit, psoLight, psoBlendUnlit, psoBlendLight };
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, pipelines[lighting + blending * 2]);
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &(const SDL_GPUTextureSamplerBinding)
|
||||||
|
{
|
||||||
|
.texture = texture,
|
||||||
|
.sampler = samplers[filter]
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
// Bind vertex & index buffers
|
||||||
|
SDL_BindGPUVertexBuffers(pass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(pass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
// Setup the view
|
||||||
|
float model[16];
|
||||||
|
Mtx_Translation(model, 0.0f, 0.0f, z);
|
||||||
|
Mtx_Rotate(model, xRot, 1.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Rotate(model, yRot, 0.0f, 1.0f, 0.0f);
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
if (lighting)
|
||||||
|
{
|
||||||
|
struct { float model[16], projection[16]; } u;
|
||||||
|
SDL_memcpy(u.model, model, sizeof(u.model));
|
||||||
|
SDL_memcpy(u.projection, projection, sizeof(u.projection));
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 1, &light, sizeof(light));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct { float modelViewProj[16], color[4]; } u;
|
||||||
|
Mtx_Multiply(u.modelViewProj, projection, model);
|
||||||
|
// 50% translucency
|
||||||
|
SDL_memcpy(u.color, (const float[4]){ 1.0f, 1.0f, 1.0f, 0.5f }, sizeof(float) * 4);
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, &u, sizeof(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw textured cube
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, SDL_arraysize(indices), 1, 0, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
|
||||||
|
const bool* keys = SDL_GetKeyboardState(NULL);
|
||||||
|
|
||||||
|
if (keys[SDL_SCANCODE_UP]) { xSpeed -= 0.01f; }
|
||||||
|
if (keys[SDL_SCANCODE_DOWN]) { xSpeed += 0.01f; }
|
||||||
|
if (keys[SDL_SCANCODE_RIGHT]) { ySpeed += 0.1f; }
|
||||||
|
if (keys[SDL_SCANCODE_LEFT]) { ySpeed -= 0.1f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEUP]) { z -= 0.02f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEDOWN]) { z += 0.02f; }
|
||||||
|
|
||||||
|
xRot += xSpeed;
|
||||||
|
yRot += ySpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson8_Key(NeHeContext* ctx, SDL_Keycode key, bool down, bool repeat)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (down && !repeat)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case SDLK_L:
|
||||||
|
lighting = !lighting;
|
||||||
|
break;
|
||||||
|
case SDLK_B:
|
||||||
|
blending = !blending;
|
||||||
|
break;
|
||||||
|
case SDLK_F:
|
||||||
|
filter = (filter + 1) % (int)SDL_arraysize(samplers);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "Tom Stanis & NeHe's Blending Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.createDepthFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
|
.init = Lesson8_Init,
|
||||||
|
.quit = Lesson8_Quit,
|
||||||
|
.resize = Lesson8_Resize,
|
||||||
|
.draw = Lesson8_Draw,
|
||||||
|
.key = Lesson8_Key
|
||||||
|
};
|
||||||
359
src/c/lesson9.c
Normal file
359
src/c/lesson9.c
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
float u, v;
|
||||||
|
} Vertex;
|
||||||
|
|
||||||
|
static const Vertex vertices[] =
|
||||||
|
{
|
||||||
|
{ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f },
|
||||||
|
{ 1.0f, -1.0f, 0.0f, 1.0f, 0.0f },
|
||||||
|
{ 1.0f, 1.0f, 0.0f, 1.0f, 1.0f },
|
||||||
|
{ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint16_t indices[] =
|
||||||
|
{
|
||||||
|
0, 1, 2,
|
||||||
|
2, 3, 0
|
||||||
|
};
|
||||||
|
|
||||||
|
static SDL_GPUGraphicsPipeline* pso = NULL;
|
||||||
|
static SDL_GPUBuffer* vtxBuffer = NULL;
|
||||||
|
static SDL_GPUBuffer* idxBuffer = NULL;
|
||||||
|
static SDL_GPUTexture* texture = NULL;
|
||||||
|
static SDL_GPUSampler* sampler = NULL;
|
||||||
|
|
||||||
|
static SDL_GPUBuffer* instanceBuffer = NULL;
|
||||||
|
static SDL_GPUTransferBuffer* instanceXferBuffer = NULL;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
float model[16];
|
||||||
|
float color[4];
|
||||||
|
} Instance;
|
||||||
|
|
||||||
|
static float projection[16];
|
||||||
|
|
||||||
|
static bool twinkle = false;
|
||||||
|
|
||||||
|
static struct Star
|
||||||
|
{
|
||||||
|
float distance, angle;
|
||||||
|
uint8_t r, g, b;
|
||||||
|
} stars[50];
|
||||||
|
|
||||||
|
static float zoom = -15.0f;
|
||||||
|
static float tilt = 90.0f;
|
||||||
|
static float spin = 0.0f;
|
||||||
|
|
||||||
|
static uint32_t rngState = 1;
|
||||||
|
static inline int RngNext(void)
|
||||||
|
{
|
||||||
|
//TODO: check which one of these matches win32
|
||||||
|
#if 0
|
||||||
|
rngState = rngState * 1103515245 + 12345;
|
||||||
|
#else
|
||||||
|
rngState = rngState * 214013 + 2531011;
|
||||||
|
#endif
|
||||||
|
return (int)((rngState >> 16) & 0x7FFF); // (s / 65536) % 32768
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool Lesson9_Init(NeHeContext* ctx)
|
||||||
|
{
|
||||||
|
SDL_GPUShader* vertexShader, * fragmentShader;
|
||||||
|
if (!NeHe_LoadShaders(ctx, &vertexShader, &fragmentShader, "lesson9",
|
||||||
|
&(const NeHeShaderProgramCreateInfo){ .vertexUniforms = 1, .fragmentSamplers = 1, .vertexStorage = 1 }))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SDL_GPUVertexAttribute vertexAttribs[] =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.location = 0,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
|
||||||
|
.offset = offsetof(Vertex, x)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.location = 1,
|
||||||
|
.buffer_slot = 0,
|
||||||
|
.format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||||
|
.offset = offsetof(Vertex, u)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pso = SDL_CreateGPUGraphicsPipeline(ctx->device, &(const SDL_GPUGraphicsPipelineCreateInfo)
|
||||||
|
{
|
||||||
|
.vertex_shader = vertexShader,
|
||||||
|
.fragment_shader = fragmentShader,
|
||||||
|
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
|
||||||
|
.vertex_input_state =
|
||||||
|
{
|
||||||
|
.vertex_buffer_descriptions = &(const SDL_GPUVertexBufferDescription)
|
||||||
|
{
|
||||||
|
.slot = 0,
|
||||||
|
.pitch = sizeof(Vertex),
|
||||||
|
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX
|
||||||
|
},
|
||||||
|
.num_vertex_buffers = 1,
|
||||||
|
.vertex_attributes = vertexAttribs,
|
||||||
|
.num_vertex_attributes = SDL_arraysize(vertexAttribs)
|
||||||
|
},
|
||||||
|
.rasterizer_state =
|
||||||
|
{
|
||||||
|
.fill_mode = SDL_GPU_FILLMODE_FILL,
|
||||||
|
.cull_mode = SDL_GPU_CULLMODE_NONE,
|
||||||
|
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE
|
||||||
|
},
|
||||||
|
.target_info =
|
||||||
|
{
|
||||||
|
.color_target_descriptions = &(const SDL_GPUColorTargetDescription)
|
||||||
|
{
|
||||||
|
.format = SDL_GetGPUSwapchainTextureFormat(ctx->device, ctx->window),
|
||||||
|
.blend_state =
|
||||||
|
{
|
||||||
|
.enable_blend = true,
|
||||||
|
.color_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||||
|
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
|
||||||
|
.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||||
|
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
|
||||||
|
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA,
|
||||||
|
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.num_color_targets = 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, fragmentShader);
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vertexShader);
|
||||||
|
if (!pso)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUGraphicsPipeline: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((texture = NeHe_LoadTexture(ctx, "Data/Star.bmp", true, false)) == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler = SDL_CreateGPUSampler(ctx->device, &(const SDL_GPUSamplerCreateInfo)
|
||||||
|
{
|
||||||
|
.mag_filter = SDL_GPU_FILTER_LINEAR,
|
||||||
|
.min_filter = SDL_GPU_FILTER_LINEAR
|
||||||
|
});
|
||||||
|
if (!sampler)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NeHe_CreateVertexIndexBuffer(ctx, &vtxBuffer, &idxBuffer,
|
||||||
|
vertices, sizeof(vertices),
|
||||||
|
indices, sizeof(indices)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int numStars = SDL_arraysize(stars);
|
||||||
|
|
||||||
|
instanceBuffer = SDL_CreateGPUBuffer(ctx->device, &(const SDL_GPUBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ,
|
||||||
|
.size = sizeof(Instance) * 2 * numStars
|
||||||
|
});
|
||||||
|
if (!instanceBuffer)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
instanceXferBuffer = SDL_CreateGPUTransferBuffer(ctx->device, &(const SDL_GPUTransferBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
.size = sizeof(Instance) * numStars
|
||||||
|
});
|
||||||
|
if (!instanceXferBuffer)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise stars
|
||||||
|
for (int i = 0; i < numStars; ++i)
|
||||||
|
{
|
||||||
|
stars[i] = (struct Star)
|
||||||
|
{
|
||||||
|
.angle = 0.0f,
|
||||||
|
.distance = 5.0f * ((float)i / (float)numStars),
|
||||||
|
.r = (uint8_t)(RngNext() % 256),
|
||||||
|
.g = (uint8_t)(RngNext() % 256),
|
||||||
|
.b = (uint8_t)(RngNext() % 256)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson9_Quit(NeHeContext* ctx)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, instanceXferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, instanceBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
SDL_ReleaseGPUSampler(ctx->device, sampler);
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, texture);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx->device, pso);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson9_Resize(NeHeContext* ctx, int width, int height)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
// Avoid division by zero by clamping height
|
||||||
|
height = SDL_max(height, 1);
|
||||||
|
// Recalculate projection matrix
|
||||||
|
Mtx_Perspective(projection, 45.0f, (float)width / (float)height, 0.1f, 100.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson9_Draw(NeHeContext* restrict ctx, SDL_GPUCommandBuffer* restrict cmd, SDL_GPUTexture* restrict swapchain)
|
||||||
|
{
|
||||||
|
const SDL_GPUColorTargetInfo colorInfo =
|
||||||
|
{
|
||||||
|
.texture = swapchain,
|
||||||
|
.clear_color = { 0.0f, 0.0f, 0.0f, 0.5f },
|
||||||
|
.load_op = SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.store_op = SDL_GPU_STOREOP_STORE
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int numStars = SDL_arraysize(stars);
|
||||||
|
|
||||||
|
// Animate stars
|
||||||
|
Instance* instances = SDL_MapGPUTransferBuffer(ctx->device, instanceXferBuffer, true);
|
||||||
|
for (int i = 0, instanceIdx = 0; i < numStars; ++i, ++instanceIdx)
|
||||||
|
{
|
||||||
|
struct Star* star = &stars[i];
|
||||||
|
Instance* instance = &instances[instanceIdx];
|
||||||
|
|
||||||
|
Mtx_Translation(instance->model, 0.0f ,0.0f, zoom);
|
||||||
|
Mtx_Rotate(instance->model, tilt, 1.0f, 0.0f, 0.0f);
|
||||||
|
Mtx_Rotate(instance->model, star->angle, 0.0f, 1.0f, 0.0f);
|
||||||
|
Mtx_Translate(instance->model, star->distance, 0.0f, 0.0f);
|
||||||
|
Mtx_Rotate(instance->model, -star->angle, 0.0f, 1.0f, 0.0f);
|
||||||
|
Mtx_Rotate(instance->model, -tilt, 1.0f, 0.0f, 0.0f);
|
||||||
|
|
||||||
|
if (twinkle)
|
||||||
|
{
|
||||||
|
instance->color[0] = (float)stars[numStars - i - 1].r / 255.0f;
|
||||||
|
instance->color[1] = (float)stars[numStars - i - 1].g / 255.0f;
|
||||||
|
instance->color[2] = (float)stars[numStars - i - 1].b / 255.0f;
|
||||||
|
instance->color[3] = 1.0f;
|
||||||
|
SDL_memcpy(instances[++instanceIdx].model, instance->model, sizeof(float) * 16);
|
||||||
|
instance = &instances[instanceIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
Mtx_Rotate(instance->model, spin, 0.0f, 0.0f, 1.0f);
|
||||||
|
instance->color[0] = (float)star->r / 255.0f;
|
||||||
|
instance->color[1] = (float)star->g / 255.0f;
|
||||||
|
instance->color[2] = (float)star->b / 255.0f;
|
||||||
|
instance->color[3] = 1.0f;
|
||||||
|
|
||||||
|
spin += 0.01f;
|
||||||
|
star->angle += (float)i / (float)numStars;
|
||||||
|
star->distance -= 0.01f;
|
||||||
|
if (star->distance < 0.0f)
|
||||||
|
{
|
||||||
|
star->distance += 5.0f;
|
||||||
|
star->r = (uint8_t)(RngNext() % 256);
|
||||||
|
star->g = (uint8_t)(RngNext() % 256);
|
||||||
|
star->b = (uint8_t)(RngNext() % 256);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_UnmapGPUTransferBuffer(ctx->device, instanceXferBuffer);
|
||||||
|
|
||||||
|
const unsigned numInstances = twinkle ? 2 * (unsigned)numStars : (unsigned)numStars;
|
||||||
|
|
||||||
|
// Upload instances buffer to the GPU
|
||||||
|
SDL_GPUCopyPass* copyPass = SDL_BeginGPUCopyPass(cmd);
|
||||||
|
SDL_UploadToGPUBuffer(copyPass, &(const SDL_GPUTransferBufferLocation)
|
||||||
|
{
|
||||||
|
.transfer_buffer = instanceXferBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, &(const SDL_GPUBufferRegion)
|
||||||
|
{
|
||||||
|
.buffer = instanceBuffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = sizeof(Instance) * numInstances
|
||||||
|
}, true);
|
||||||
|
SDL_EndGPUCopyPass(copyPass);
|
||||||
|
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass(cmd, &colorInfo, 1, NULL);
|
||||||
|
SDL_BindGPUGraphicsPipeline(renderPass, pso);
|
||||||
|
|
||||||
|
// Bind particle texture
|
||||||
|
SDL_BindGPUFragmentSamplers(renderPass, 0, &(const SDL_GPUTextureSamplerBinding)
|
||||||
|
{
|
||||||
|
.texture = texture,
|
||||||
|
.sampler = sampler
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
SDL_BindGPUVertexBuffers(renderPass, 0, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, 1);
|
||||||
|
SDL_BindGPUVertexStorageBuffers(renderPass, 0, &instanceBuffer, 1);
|
||||||
|
SDL_BindGPUIndexBuffer(renderPass, &(const SDL_GPUBufferBinding)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, SDL_GPU_INDEXELEMENTSIZE_16BIT);
|
||||||
|
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, projection, sizeof(projection));
|
||||||
|
SDL_DrawGPUIndexedPrimitives(renderPass, SDL_arraysize(indices), numInstances, 0, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(renderPass);
|
||||||
|
|
||||||
|
const bool* keys = SDL_GetKeyboardState(NULL);
|
||||||
|
|
||||||
|
if (keys[SDL_SCANCODE_UP]) { tilt -= 0.5f; }
|
||||||
|
if (keys[SDL_SCANCODE_DOWN]) { tilt += 0.5f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEUP]) { zoom -= 0.2f; }
|
||||||
|
if (keys[SDL_SCANCODE_PAGEDOWN]) { zoom += 0.2f; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Lesson9_Key(NeHeContext* ctx, SDL_Keycode key, bool down, bool repeat)
|
||||||
|
{
|
||||||
|
(void)ctx;
|
||||||
|
|
||||||
|
if (down && !repeat)
|
||||||
|
{
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case SDLK_T:
|
||||||
|
twinkle = !twinkle;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const struct AppConfig appConfig =
|
||||||
|
{
|
||||||
|
.title = "NeHe's Animated Blended Textures Tutorial",
|
||||||
|
.width = 640, .height = 480,
|
||||||
|
.init = Lesson9_Init,
|
||||||
|
.quit = Lesson9_Quit,
|
||||||
|
.resize = Lesson9_Resize,
|
||||||
|
.draw = Lesson9_Draw,
|
||||||
|
.key = Lesson9_Key
|
||||||
|
};
|
||||||
132
src/c/matrix.c
Normal file
132
src/c/matrix.c
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "matrix.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern inline void Mtx_Identity(float m[16]);
|
||||||
|
extern inline void Mtx_Translation(float m[16], float x, float y, float z);
|
||||||
|
|
||||||
|
static void MakeRotation(float m[9], float c, float s, float x, float y, float z)
|
||||||
|
{
|
||||||
|
const float rc = 1.f - c;
|
||||||
|
const float rcx = x * rc, rcy = y * rc, rcz = z * rc;
|
||||||
|
const float sx = x * s, sy = y * s, sz = z * s;
|
||||||
|
|
||||||
|
m[0] = rcx * x + c;
|
||||||
|
m[3] = rcx * y - sz;
|
||||||
|
m[6] = rcx * z + sy;
|
||||||
|
|
||||||
|
m[1] = rcy * x + sz;
|
||||||
|
m[4] = rcy * y + c;
|
||||||
|
m[7] = rcy * z - sx;
|
||||||
|
|
||||||
|
m[2] = rcz * x - sy;
|
||||||
|
m[5] = rcz * y + sx;
|
||||||
|
m[8] = rcz * z + c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MakeGLRotation(float m[9], float angle, float x, float y, float z)
|
||||||
|
{
|
||||||
|
// Treat inputs like glRotatef
|
||||||
|
const float theta = angle * (SDL_PI_F / 180.0f);
|
||||||
|
const float axisMag = SDL_sqrtf(x * x + y * y + z * z);
|
||||||
|
if (SDL_fabsf(axisMag - 1.f) > SDL_FLT_EPSILON)
|
||||||
|
{
|
||||||
|
x /= axisMag;
|
||||||
|
y /= axisMag;
|
||||||
|
z /= axisMag;
|
||||||
|
}
|
||||||
|
|
||||||
|
MakeRotation(m, SDL_cosf(theta), SDL_sinf(theta), x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mtx_Rotation(float m[16], float angle, float x, float y, float z)
|
||||||
|
{
|
||||||
|
float r[9];
|
||||||
|
MakeGLRotation(r, angle, x, y, z);
|
||||||
|
|
||||||
|
m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0.0f;
|
||||||
|
m[15] = 1.0f;
|
||||||
|
|
||||||
|
m[0] = r[0]; m[1] = r[1]; m[2] = r[2];
|
||||||
|
m[4] = r[3]; m[5] = r[4]; m[6] = r[5];
|
||||||
|
m[8] = r[6]; m[9] = r[7]; m[10] = r[8];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mtx_Perspective(float m[16], float fovy, float aspect, float near, float far)
|
||||||
|
{
|
||||||
|
const float h = 1.0f / SDL_tanf(fovy * (SDL_PI_F / 180.0f) * 0.5f);
|
||||||
|
const float w = h / aspect;
|
||||||
|
const float invClipRng = 1.0f / (far - near);
|
||||||
|
const float zh = -(far + near) * invClipRng;
|
||||||
|
const float zl = -(2.0f * far * near) * invClipRng;
|
||||||
|
|
||||||
|
/*
|
||||||
|
[w 0 0 0]
|
||||||
|
[0 h 0 0]
|
||||||
|
[0 0 zh zl]
|
||||||
|
[0 0 -1 0]
|
||||||
|
*/
|
||||||
|
m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[12] = m[13] = m[15] = 0.0f;
|
||||||
|
m[0] = w;
|
||||||
|
m[5] = h;
|
||||||
|
m[10] = zh;
|
||||||
|
m[14] = zl;
|
||||||
|
m[11] = -1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mtx_Multiply(float m[16], const float l[16], const float r[16])
|
||||||
|
{
|
||||||
|
int i = 0;
|
||||||
|
for (int col = 0; col < 4; ++col)
|
||||||
|
{
|
||||||
|
for (int row = 0; row < 4; ++row)
|
||||||
|
{
|
||||||
|
float a = 0.f;
|
||||||
|
for (int j = 0; j < 4; ++j)
|
||||||
|
{
|
||||||
|
a += l[j * 4 + row] * r[col * 4 + j];
|
||||||
|
}
|
||||||
|
m[i++] = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mtx_Translate(float m[16], float x, float y, float z)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
m = { [1 0 0 x]
|
||||||
|
[0 1 0 y]
|
||||||
|
[0 0 1 z]
|
||||||
|
[0 0 0 1] } * m
|
||||||
|
*/
|
||||||
|
m[12] += x * m[0] + y * m[4] + z * m[8];
|
||||||
|
m[13] += x * m[1] + y * m[5] + z * m[9];
|
||||||
|
m[14] += x * m[2] + y * m[6] + z * m[10];
|
||||||
|
m[15] += x * m[3] + y * m[7] + z * m[11];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mtx_Rotate(float m[16], float angle, float x, float y, float z)
|
||||||
|
{
|
||||||
|
// Set up temporaries
|
||||||
|
float tmp[12], r[9];
|
||||||
|
SDL_memcpy(tmp, m, sizeof(float) * 12);
|
||||||
|
MakeGLRotation(r, angle, x, y, z);
|
||||||
|
|
||||||
|
// Partial matrix multiplication
|
||||||
|
m[0] = r[0] * tmp[0] + r[1] * tmp[4] + r[2] * tmp[8];
|
||||||
|
m[1] = r[0] * tmp[1] + r[1] * tmp[5] + r[2] * tmp[9];
|
||||||
|
m[2] = r[0] * tmp[2] + r[1] * tmp[6] + r[2] * tmp[10];
|
||||||
|
m[3] = r[0] * tmp[3] + r[1] * tmp[7] + r[2] * tmp[11];
|
||||||
|
m[4] = r[3] * tmp[0] + r[4] * tmp[4] + r[5] * tmp[8];
|
||||||
|
m[5] = r[3] * tmp[1] + r[4] * tmp[5] + r[5] * tmp[9];
|
||||||
|
m[6] = r[3] * tmp[2] + r[4] * tmp[6] + r[5] * tmp[10];
|
||||||
|
m[7] = r[3] * tmp[3] + r[4] * tmp[7] + r[5] * tmp[11];
|
||||||
|
m[8] = r[6] * tmp[0] + r[7] * tmp[4] + r[8] * tmp[8];
|
||||||
|
m[9] = r[6] * tmp[1] + r[7] * tmp[5] + r[8] * tmp[9];
|
||||||
|
m[10] = r[6] * tmp[2] + r[7] * tmp[6] + r[8] * tmp[10];
|
||||||
|
m[11] = r[6] * tmp[3] + r[7] * tmp[7] + r[8] * tmp[11];
|
||||||
|
}
|
||||||
36
src/c/matrix.h
Normal file
36
src/c/matrix.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef MATRIX_H
|
||||||
|
#define MATRIX_H
|
||||||
|
|
||||||
|
#include <SDL3/SDL_stdinc.h>
|
||||||
|
|
||||||
|
inline void Mtx_Identity(float m[16])
|
||||||
|
{
|
||||||
|
SDL_memcpy(m, (float[])
|
||||||
|
{
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
0, 0, 0, 1
|
||||||
|
}, sizeof(float) * 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void Mtx_Translation(float m[16], float x, float y, float z)
|
||||||
|
{
|
||||||
|
SDL_memcpy(m, (float[])
|
||||||
|
{
|
||||||
|
1, 0, 0, 0,
|
||||||
|
0, 1, 0, 0,
|
||||||
|
0, 0, 1, 0,
|
||||||
|
x, y, z, 1
|
||||||
|
}, sizeof(float) * 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Mtx_Rotation(float m[16], float angle, float x, float y, float z);
|
||||||
|
void Mtx_Perspective(float m[16], float fovy, float aspect, float near, float far);
|
||||||
|
|
||||||
|
void Mtx_Multiply(float m[16], const float l[16], const float r[16]);
|
||||||
|
|
||||||
|
void Mtx_Translate(float m[16], float x, float y, float z);
|
||||||
|
void Mtx_Rotate(float m[16], float angle, float x, float y, float z);
|
||||||
|
|
||||||
|
#endif//MATRIX_H
|
||||||
649
src/c/nehe.c
Normal file
649
src/c/nehe.c
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nehe.h"
|
||||||
|
|
||||||
|
|
||||||
|
bool NeHe_InitGPU(NeHeContext* ctx, const char* title, int width, int height)
|
||||||
|
{
|
||||||
|
// Create window
|
||||||
|
ctx->window = SDL_CreateWindow(title, width, height, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||||
|
if (!ctx->window)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateWindow: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open GPU device
|
||||||
|
const SDL_GPUShaderFormat formats =
|
||||||
|
SDL_GPU_SHADERFORMAT_METALLIB | SDL_GPU_SHADERFORMAT_MSL |
|
||||||
|
SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL;
|
||||||
|
ctx->device = SDL_CreateGPUDevice(formats, true, NULL);
|
||||||
|
if (!ctx->device)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUDevice: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach window to the GPU device
|
||||||
|
if (!SDL_ClaimWindowForGPUDevice(ctx->device, ctx->window))
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_ClaimWindowForGPUDevice: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable VSync
|
||||||
|
SDL_SetGPUSwapchainParameters(ctx->device, ctx->window,
|
||||||
|
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
||||||
|
SDL_GPU_PRESENTMODE_VSYNC);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeHe_SetupDepthTexture(NeHeContext* ctx, uint32_t width, uint32_t height,
|
||||||
|
SDL_GPUTextureFormat format, float clearDepth)
|
||||||
|
{
|
||||||
|
if (ctx->depthTexture)
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(ctx->device, ctx->depthTexture);
|
||||||
|
ctx->depthTexture = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PropertiesID props = SDL_CreateProperties();
|
||||||
|
if (props == 0)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateProperties: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Workaround for https://github.com/libsdl-org/SDL/issues/10758
|
||||||
|
SDL_SetFloatProperty(props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, clearDepth);
|
||||||
|
|
||||||
|
SDL_GPUTexture* texture = SDL_CreateGPUTexture(ctx->device, &(const SDL_GPUTextureCreateInfo)
|
||||||
|
{
|
||||||
|
.type = SDL_GPU_TEXTURETYPE_2D,
|
||||||
|
.format = format,
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.layer_count_or_depth = 1,
|
||||||
|
.num_levels = 1,
|
||||||
|
.sample_count = SDL_GPU_SAMPLECOUNT_1,
|
||||||
|
.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
||||||
|
.props = props
|
||||||
|
});
|
||||||
|
SDL_DestroyProperties(props);
|
||||||
|
if (!texture)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUTexture: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetGPUTextureName(ctx->device, texture, "Depth Buffer Texture");
|
||||||
|
ctx->depthTexture = texture;
|
||||||
|
ctx->depthTextureWidth = width;
|
||||||
|
ctx->depthTextureHeight = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* NeHe_ResourcePath(const NeHeContext* restrict ctx, const char* const restrict resourcePath)
|
||||||
|
{
|
||||||
|
SDL_assert(ctx && ctx->baseDir && resourcePath);
|
||||||
|
|
||||||
|
// Build path to resouce: "{baseDir}/{resourcePath}"
|
||||||
|
const size_t baseLen = SDL_strlen(ctx->baseDir);
|
||||||
|
const size_t resourcePathLen = SDL_strlen(resourcePath);
|
||||||
|
char* path = SDL_malloc(baseLen + resourcePathLen + 1);
|
||||||
|
if (!path)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
SDL_memcpy(path, ctx->baseDir, baseLen);
|
||||||
|
SDL_memcpy(&path[baseLen], resourcePath, resourcePathLen);
|
||||||
|
path[baseLen + resourcePathLen] = '\0';
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern inline SDL_IOStream* NeHe_OpenResource(const NeHeContext* restrict ctx,
|
||||||
|
const char* restrict resourcePath,
|
||||||
|
const char* restrict mode);
|
||||||
|
|
||||||
|
SDL_GPUTexture* NeHe_LoadTexture(NeHeContext* restrict ctx, const char* const restrict resourcePath,
|
||||||
|
bool flipVertical, bool genMipmaps)
|
||||||
|
{
|
||||||
|
char* path = NeHe_ResourcePath(ctx, resourcePath);
|
||||||
|
if (!path)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load image into a surface
|
||||||
|
SDL_Surface* image = SDL_LoadBMP(path);
|
||||||
|
SDL_free(path);
|
||||||
|
if (!image)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_LoadBMP: %s", SDL_GetError());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flip surface if requested
|
||||||
|
if (flipVertical && !SDL_FlipSurface(image, SDL_FLIP_VERTICAL))
|
||||||
|
{
|
||||||
|
SDL_DestroySurface(image);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload texture to GPU
|
||||||
|
SDL_GPUTexture* texture = NeHe_CreateGPUTextureFromSurface(ctx, image, genMipmaps);
|
||||||
|
SDL_DestroySurface(image);
|
||||||
|
if (!texture)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_GPUTexture* CreateTextureFromPixels(SDL_GPUDevice* restrict device,
|
||||||
|
const void* restrict data, size_t dataSize, const SDL_GPUTextureCreateInfo* restrict createInfo, bool genMipmaps)
|
||||||
|
{
|
||||||
|
SDL_assert(dataSize <= UINT32_MAX);
|
||||||
|
|
||||||
|
SDL_GPUTexture* texture = SDL_CreateGPUTexture(device, createInfo);
|
||||||
|
if (!texture)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUTexture: %s", SDL_GetError());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and copy image data to a transfer buffer
|
||||||
|
SDL_GPUTransferBuffer* xferBuffer = SDL_CreateGPUTransferBuffer(device, &(const SDL_GPUTransferBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
.size = (Uint32)dataSize
|
||||||
|
});
|
||||||
|
if (!xferBuffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTexture(device, texture);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* map = SDL_MapGPUTransferBuffer(device, xferBuffer, false);
|
||||||
|
if (!map)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_MapGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(device, xferBuffer);
|
||||||
|
SDL_ReleaseGPUTexture(device, texture);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
SDL_memcpy(map, data, dataSize);
|
||||||
|
SDL_UnmapGPUTransferBuffer(device, xferBuffer);
|
||||||
|
|
||||||
|
// Upload the transfer data to the GPU resources
|
||||||
|
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device);
|
||||||
|
if (!cmd)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AcquireGPUCommandBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(device, xferBuffer);
|
||||||
|
SDL_ReleaseGPUTexture(device, texture);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(cmd);
|
||||||
|
SDL_UploadToGPUTexture(pass, &(const SDL_GPUTextureTransferInfo)
|
||||||
|
{
|
||||||
|
.transfer_buffer = xferBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, &(const SDL_GPUTextureRegion)
|
||||||
|
{
|
||||||
|
.texture = texture,
|
||||||
|
.w = createInfo->width,
|
||||||
|
.h = createInfo->height,
|
||||||
|
.d = createInfo->layer_count_or_depth
|
||||||
|
}, false);
|
||||||
|
SDL_EndGPUCopyPass(pass);
|
||||||
|
|
||||||
|
if (genMipmaps)
|
||||||
|
{
|
||||||
|
SDL_GenerateMipmapsForGPUTexture(cmd, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(device, xferBuffer);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUTexture* NeHe_CreateGPUTextureFromSurface(NeHeContext* restrict ctx, const SDL_Surface* restrict surface,
|
||||||
|
bool genMipmaps)
|
||||||
|
{
|
||||||
|
SDL_GPUTextureCreateInfo info;
|
||||||
|
SDL_zero(info);
|
||||||
|
info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||||
|
info.format = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||||
|
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||||
|
info.width = (Uint32)surface->w;
|
||||||
|
info.height = (Uint32)surface->h;
|
||||||
|
info.layer_count_or_depth = 1;
|
||||||
|
info.num_levels = 1;
|
||||||
|
|
||||||
|
bool needsConvert = false;
|
||||||
|
switch (surface->format)
|
||||||
|
{
|
||||||
|
// FIMXE: I'm not sure that these are endian-safe
|
||||||
|
case SDL_PIXELFORMAT_RGBA32: info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; break;
|
||||||
|
case SDL_PIXELFORMAT_RGBA64: info.format = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM; break;
|
||||||
|
case SDL_PIXELFORMAT_RGB565: info.format = SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM; break;
|
||||||
|
case SDL_PIXELFORMAT_ARGB1555: info.format = SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM; break;
|
||||||
|
case SDL_PIXELFORMAT_BGRA4444: info.format = SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM; break;
|
||||||
|
case SDL_PIXELFORMAT_BGRA32: info.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM; break;
|
||||||
|
case SDL_PIXELFORMAT_RGBA64_FLOAT: info.format = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT; break;
|
||||||
|
case SDL_PIXELFORMAT_RGBA128_FLOAT: info.format = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT; break;
|
||||||
|
default:
|
||||||
|
needsConvert = true;
|
||||||
|
info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t dataSize = (size_t)surface->w * (size_t)surface->h;
|
||||||
|
void* data = NULL;
|
||||||
|
SDL_Surface* conv = NULL;
|
||||||
|
if (needsConvert)
|
||||||
|
{
|
||||||
|
// Convert pixel format if required
|
||||||
|
if ((conv = SDL_ConvertSurface((SDL_Surface*)surface, SDL_PIXELFORMAT_ABGR8888)) == NULL)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_ConvertSurface: %s", SDL_GetError());
|
||||||
|
SDL_free(data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
dataSize *= SDL_BYTESPERPIXEL(conv->format);
|
||||||
|
data = conv->pixels;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataSize *= SDL_BYTESPERPIXEL(surface->format);
|
||||||
|
data = surface->pixels;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (genMipmaps)
|
||||||
|
{
|
||||||
|
info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||||
|
// floor(log₂(max(𝑤,ℎ)) + 1
|
||||||
|
info.num_levels = (Uint32)SDL_MostSignificantBitIndex32(SDL_max(info.width, info.height)) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUTexture* texture = CreateTextureFromPixels(ctx->device, data, dataSize, &info, genMipmaps);
|
||||||
|
SDL_DestroySurface(conv);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* ReadBlob(const char* const restrict path, size_t* restrict outLength)
|
||||||
|
{
|
||||||
|
SDL_ClearError();
|
||||||
|
SDL_IOStream* file = SDL_IOFromFile(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_IOFromFile: %s", SDL_GetError());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate a buffer of the size of the file
|
||||||
|
if (SDL_SeekIO(file, 0, SDL_IO_SEEK_END) == -1)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_SeekIO: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
const int64_t size = SDL_TellIO(file);
|
||||||
|
char* data;
|
||||||
|
if (size < 0 || (data = SDL_malloc((size_t)size)) == NULL)
|
||||||
|
{
|
||||||
|
SDL_CloseIO(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (SDL_SeekIO(file, 0, SDL_IO_SEEK_SET) != 0)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_SeekIO: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_ClearError();
|
||||||
|
// Read the file contents into the buffer
|
||||||
|
const size_t read = SDL_ReadIO(file, data, (size_t)size);
|
||||||
|
if (read == 0 && SDL_GetIOStatus(file) == SDL_IO_STATUS_ERROR)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_ReadIO: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
if (!SDL_CloseIO(file))
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CloseIO: %s", SDL_GetError());
|
||||||
|
}
|
||||||
|
if (read != (size_t)size)
|
||||||
|
{
|
||||||
|
SDL_free(data);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outLength = (size_t)size;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_GPUShader* LoadShaderBlob(NeHeContext* restrict ctx,
|
||||||
|
const char* restrict code, size_t codeLen,
|
||||||
|
const NeHeShaderProgramCreateInfo* restrict info,
|
||||||
|
SDL_GPUShaderFormat format, SDL_GPUShaderStage type,
|
||||||
|
const char* const restrict main)
|
||||||
|
{
|
||||||
|
if (!code)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUShader* shader = SDL_CreateGPUShader(ctx->device, &(const SDL_GPUShaderCreateInfo)
|
||||||
|
{
|
||||||
|
.code_size = codeLen,
|
||||||
|
.code = (const Uint8*)code,
|
||||||
|
.entrypoint = main,
|
||||||
|
.format = format,
|
||||||
|
.stage = type,
|
||||||
|
.num_samplers = (type == SDL_GPU_SHADERSTAGE_FRAGMENT) ? info->fragmentSamplers : 0,
|
||||||
|
.num_storage_buffers = (type == SDL_GPU_SHADERSTAGE_VERTEX) ? info->vertexUniforms : 0,
|
||||||
|
.num_uniform_buffers = (type == SDL_GPU_SHADERSTAGE_VERTEX) ? info->vertexUniforms : 0,
|
||||||
|
});
|
||||||
|
if (!shader)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUShader: %s", SDL_GetError());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SDL_GPUShader* LoadShader(NeHeContext* restrict ctx, const char* restrict path,
|
||||||
|
const NeHeShaderProgramCreateInfo* restrict info, SDL_GPUShaderFormat format, SDL_GPUShaderStage type,
|
||||||
|
const char* const restrict main)
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
char* data = ReadBlob(path, &size);
|
||||||
|
SDL_GPUShader *shader = LoadShaderBlob(ctx, data, size, info, format, type, main);
|
||||||
|
SDL_free(data);
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeHe_LoadShaders(NeHeContext* restrict ctx,
|
||||||
|
SDL_GPUShader** restrict outVertex,
|
||||||
|
SDL_GPUShader** restrict outFragment,
|
||||||
|
const char* restrict name,
|
||||||
|
const NeHeShaderProgramCreateInfo* restrict info)
|
||||||
|
//unsigned vertexUniforms, unsigned fragmentSamplers, unsigned vertexStorage)
|
||||||
|
{
|
||||||
|
SDL_GPUShader *vtxShader = NULL, *frgShader = NULL;
|
||||||
|
|
||||||
|
// Build path to shader: "{base}/Shaders/{name}.{ext}"
|
||||||
|
const char* resources = SDL_GetBasePath(); // Resources directory
|
||||||
|
const size_t resourcesLen = SDL_strlen(resources);
|
||||||
|
const size_t nameLen = SDL_strlen(name);
|
||||||
|
const size_t basenameLen = resourcesLen + 8 + nameLen;
|
||||||
|
char* path = SDL_malloc(basenameLen + 10);
|
||||||
|
if (!path)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SDL_memcpy(path, resources, resourcesLen);
|
||||||
|
SDL_memcpy(&path[resourcesLen], "Shaders", 7);
|
||||||
|
path[resourcesLen + 7] = resources[resourcesLen - 1]; // Copy path separator
|
||||||
|
SDL_memcpy(&path[resourcesLen + 8], name, nameLen);
|
||||||
|
|
||||||
|
const SDL_GPUShaderFormat availableFormats = SDL_GetGPUShaderFormats(ctx->device);
|
||||||
|
if (availableFormats & (SDL_GPU_SHADERFORMAT_METALLIB | SDL_GPU_SHADERFORMAT_MSL))
|
||||||
|
{
|
||||||
|
size_t size;
|
||||||
|
if (availableFormats & SDL_GPU_SHADERFORMAT_METALLIB) // Apple Metal (compiled library)
|
||||||
|
{
|
||||||
|
const SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_METALLIB;
|
||||||
|
SDL_memcpy(&path[basenameLen], ".metallib", 10);
|
||||||
|
char* lib = ReadBlob(path, &size);
|
||||||
|
vtxShader = LoadShaderBlob(ctx, lib, size, info, format, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain");
|
||||||
|
frgShader = LoadShaderBlob(ctx, lib, size, info, format, SDL_GPU_SHADERSTAGE_FRAGMENT, "FragmentMain");
|
||||||
|
SDL_free(lib);
|
||||||
|
}
|
||||||
|
if ((!vtxShader || !frgShader) && availableFormats & SDL_GPU_SHADERFORMAT_MSL) // Apple Metal (source)
|
||||||
|
{
|
||||||
|
const SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_MSL;
|
||||||
|
SDL_memcpy(&path[basenameLen], ".metal", 7);
|
||||||
|
char* src = ReadBlob(path, &size);
|
||||||
|
if (!vtxShader)
|
||||||
|
vtxShader = LoadShaderBlob(ctx, src, size, info, format, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain");
|
||||||
|
if (!frgShader)
|
||||||
|
frgShader = LoadShaderBlob(ctx, src, size, info, format, SDL_GPU_SHADERSTAGE_FRAGMENT, "FragmentMain");
|
||||||
|
SDL_free(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (availableFormats & SDL_GPU_SHADERFORMAT_SPIRV) // Vulkan
|
||||||
|
{
|
||||||
|
const SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||||
|
SDL_memcpy(&path[basenameLen], ".vtx.spv", 9);
|
||||||
|
vtxShader = LoadShader(ctx, path, info, format, SDL_GPU_SHADERSTAGE_VERTEX, "main");
|
||||||
|
SDL_memcpy(&path[basenameLen], ".frg.spv", 9);
|
||||||
|
frgShader = LoadShader(ctx, path, info, format, SDL_GPU_SHADERSTAGE_FRAGMENT, "main");
|
||||||
|
}
|
||||||
|
else if (availableFormats & SDL_GPU_SHADERFORMAT_DXIL) // Direct3D 12 Shader Model 6.0
|
||||||
|
{
|
||||||
|
const SDL_GPUShaderFormat format = SDL_GPU_SHADERFORMAT_DXIL;
|
||||||
|
SDL_memcpy(&path[basenameLen], ".vtx.dxb", 9);
|
||||||
|
vtxShader = LoadShader(ctx, path, info, format, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain");
|
||||||
|
SDL_memcpy(&path[basenameLen], ".pxl.dxb", 9);
|
||||||
|
frgShader = LoadShader(ctx, path, info, format, SDL_GPU_SHADERSTAGE_FRAGMENT, "PixelMain");
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(path);
|
||||||
|
if (!vtxShader || !frgShader)
|
||||||
|
{
|
||||||
|
if (vtxShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, vtxShader);
|
||||||
|
if (frgShader)
|
||||||
|
SDL_ReleaseGPUShader(ctx->device, frgShader);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*outVertex = vtxShader;
|
||||||
|
*outFragment = frgShader;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_GPUBuffer* NeHe_CreateVertexBuffer(NeHeContext* restrict ctx, const void* restrict vertices, uint32_t verticesSize)
|
||||||
|
{
|
||||||
|
// Create vertex data buffer
|
||||||
|
SDL_GPUBuffer* buffer = SDL_CreateGPUBuffer(ctx->device, &(const SDL_GPUBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_BUFFERUSAGE_VERTEX,
|
||||||
|
.size = verticesSize
|
||||||
|
});
|
||||||
|
if (!buffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUBuffer: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create vertex transfer buffer
|
||||||
|
SDL_GPUTransferBuffer* xferBuffer = SDL_CreateGPUTransferBuffer(ctx->device, &(const SDL_GPUTransferBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
.size = verticesSize
|
||||||
|
});
|
||||||
|
if (!xferBuffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map transfer buffer and copy the vertex data
|
||||||
|
Uint8* map = SDL_MapGPUTransferBuffer(ctx->device, xferBuffer, false);
|
||||||
|
if (!map)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_MapGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, xferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SDL_memcpy(map, vertices, (size_t)verticesSize);
|
||||||
|
SDL_UnmapGPUTransferBuffer(ctx->device, xferBuffer);
|
||||||
|
|
||||||
|
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(ctx->device);
|
||||||
|
if (!cmd)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AcquireGPUCommandBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, xferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload the vertex & index data into the GPU buffer(s)
|
||||||
|
SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(cmd);
|
||||||
|
SDL_UploadToGPUBuffer(pass, &(const SDL_GPUTransferBufferLocation)
|
||||||
|
{
|
||||||
|
.transfer_buffer = xferBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, &(const SDL_GPUBufferRegion)
|
||||||
|
{
|
||||||
|
.buffer = buffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = verticesSize
|
||||||
|
}, false);
|
||||||
|
SDL_EndGPUCopyPass(pass);
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd);
|
||||||
|
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, xferBuffer);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NeHe_CreateVertexIndexBuffer(NeHeContext* restrict ctx,
|
||||||
|
SDL_GPUBuffer** restrict outVertexBuffer,
|
||||||
|
SDL_GPUBuffer** restrict outIndexBuffer,
|
||||||
|
const void* restrict vertices, uint32_t verticesSize,
|
||||||
|
const void* restrict indices, uint32_t indicesSize)
|
||||||
|
{
|
||||||
|
// Create vertex data buffer
|
||||||
|
SDL_GPUBuffer* vtxBuffer = SDL_CreateGPUBuffer(ctx->device, &(const SDL_GPUBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_BUFFERUSAGE_VERTEX,
|
||||||
|
.size = verticesSize
|
||||||
|
});
|
||||||
|
if (!vtxBuffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUBuffer: %s", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create index data buffer
|
||||||
|
SDL_GPUBuffer* idxBuffer = SDL_CreateGPUBuffer(ctx->device, &(const SDL_GPUBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_BUFFERUSAGE_INDEX,
|
||||||
|
.size = indicesSize
|
||||||
|
});
|
||||||
|
if (!idxBuffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create vertex transfer buffer
|
||||||
|
SDL_GPUTransferBuffer* vtxXferBuffer = SDL_CreateGPUTransferBuffer(ctx->device, &(const SDL_GPUTransferBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
.size = verticesSize
|
||||||
|
});
|
||||||
|
if (!vtxXferBuffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create index transfer buffer
|
||||||
|
SDL_GPUTransferBuffer* idxXferBuffer = SDL_CreateGPUTransferBuffer(ctx->device, &(const SDL_GPUTransferBufferCreateInfo)
|
||||||
|
{
|
||||||
|
.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
.size = indicesSize
|
||||||
|
});
|
||||||
|
if (!idxXferBuffer)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_CreateGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, vtxXferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map transfer buffer and copy the vertex data
|
||||||
|
Uint8* map = SDL_MapGPUTransferBuffer(ctx->device, vtxXferBuffer, false);
|
||||||
|
if (!map)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_MapGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, idxXferBuffer);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, vtxXferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SDL_memcpy(map, vertices, (size_t)verticesSize);
|
||||||
|
SDL_UnmapGPUTransferBuffer(ctx->device, vtxXferBuffer);
|
||||||
|
|
||||||
|
// Map transfer buffer and copy the index data
|
||||||
|
map = SDL_MapGPUTransferBuffer(ctx->device, idxXferBuffer, false);
|
||||||
|
if (!map)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_MapGPUTransferBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, idxXferBuffer);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, vtxXferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SDL_memcpy(map, indices, (size_t)indicesSize);
|
||||||
|
SDL_UnmapGPUTransferBuffer(ctx->device, idxXferBuffer);
|
||||||
|
|
||||||
|
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(ctx->device);
|
||||||
|
if (!cmd)
|
||||||
|
{
|
||||||
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AcquireGPUCommandBuffer: %s", SDL_GetError());
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, idxXferBuffer);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, vtxXferBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, idxBuffer);
|
||||||
|
SDL_ReleaseGPUBuffer(ctx->device, vtxBuffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload the vertex & index data into the GPU buffer(s)
|
||||||
|
SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(cmd);
|
||||||
|
SDL_UploadToGPUBuffer(pass, &(const SDL_GPUTransferBufferLocation)
|
||||||
|
{
|
||||||
|
.transfer_buffer = vtxXferBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, &(const SDL_GPUBufferRegion)
|
||||||
|
{
|
||||||
|
.buffer = vtxBuffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = verticesSize
|
||||||
|
}, false);
|
||||||
|
SDL_UploadToGPUBuffer(pass, &(const SDL_GPUTransferBufferLocation)
|
||||||
|
{
|
||||||
|
.transfer_buffer = idxXferBuffer,
|
||||||
|
.offset = 0
|
||||||
|
}, &(const SDL_GPUBufferRegion)
|
||||||
|
{
|
||||||
|
.buffer = idxBuffer,
|
||||||
|
.offset = 0,
|
||||||
|
.size = indicesSize
|
||||||
|
}, false);
|
||||||
|
SDL_EndGPUCopyPass(pass);
|
||||||
|
SDL_SubmitGPUCommandBuffer(cmd);
|
||||||
|
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, idxXferBuffer);
|
||||||
|
SDL_ReleaseGPUTransferBuffer(ctx->device, vtxXferBuffer);
|
||||||
|
|
||||||
|
*outVertexBuffer = vtxBuffer;
|
||||||
|
*outIndexBuffer = idxBuffer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
62
src/c/nehe.h
Normal file
62
src/c/nehe.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#ifndef NEHE_H
|
||||||
|
#define NEHE_H
|
||||||
|
|
||||||
|
#include "application.h"
|
||||||
|
#include "matrix.h"
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
typedef struct NeHeContext
|
||||||
|
{
|
||||||
|
SDL_Window* window;
|
||||||
|
SDL_GPUDevice* device;
|
||||||
|
SDL_GPUTexture* depthTexture;
|
||||||
|
uint32_t depthTextureWidth, depthTextureHeight;
|
||||||
|
|
||||||
|
const char* baseDir;
|
||||||
|
|
||||||
|
} NeHeContext;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
unsigned vertexUniforms;
|
||||||
|
unsigned vertexStorage;
|
||||||
|
unsigned fragmentSamplers;
|
||||||
|
} NeHeShaderProgramCreateInfo;
|
||||||
|
|
||||||
|
bool NeHe_InitGPU(NeHeContext* ctx, const char* title, int width, int height);
|
||||||
|
bool NeHe_SetupDepthTexture(NeHeContext* ctx, uint32_t width, uint32_t height,
|
||||||
|
SDL_GPUTextureFormat format, float clearDepth);
|
||||||
|
char* NeHe_ResourcePath(const NeHeContext* restrict ctx, const char* restrict resourcePath);
|
||||||
|
inline SDL_IOStream* NeHe_OpenResource(const NeHeContext* restrict ctx,
|
||||||
|
const char* const restrict resourcePath,
|
||||||
|
const char* const restrict mode)
|
||||||
|
{
|
||||||
|
char* path = NeHe_ResourcePath(ctx, resourcePath);
|
||||||
|
if (!path)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
SDL_IOStream* file = SDL_IOFromFile(path, mode);
|
||||||
|
SDL_free(path);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
SDL_GPUTexture* NeHe_LoadTexture(NeHeContext* restrict ctx, const char* restrict resourcePath,
|
||||||
|
bool flipVertical, bool genMipmaps);
|
||||||
|
SDL_GPUTexture* NeHe_CreateGPUTextureFromSurface(NeHeContext* restrict ctx, const SDL_Surface* restrict surface,
|
||||||
|
bool genMipmaps);
|
||||||
|
bool NeHe_LoadShaders(NeHeContext* restrict ctx,
|
||||||
|
SDL_GPUShader** restrict outVertex,
|
||||||
|
SDL_GPUShader** restrict outFragment,
|
||||||
|
const char* restrict name,
|
||||||
|
const NeHeShaderProgramCreateInfo* restrict info);
|
||||||
|
SDL_GPUBuffer* NeHe_CreateVertexBuffer(NeHeContext* restrict ctx, const void* restrict vertices, uint32_t verticesSize);
|
||||||
|
bool NeHe_CreateVertexIndexBuffer(NeHeContext* restrict ctx,
|
||||||
|
SDL_GPUBuffer** restrict outVertexBuffer,
|
||||||
|
SDL_GPUBuffer** restrict outIndexBuffer,
|
||||||
|
const void* restrict vertices, uint32_t verticesSize,
|
||||||
|
const void* restrict indices, uint32_t indicesSize);
|
||||||
|
|
||||||
|
#endif//NEHE_H
|
||||||
36
src/shaders/lesson2.metal
Normal file
36
src/shaders/lesson2.metal
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
struct VertexInput
|
||||||
|
{
|
||||||
|
float3 position [[attribute(0)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexUniform
|
||||||
|
{
|
||||||
|
metal::float4x4 viewproj;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex2Fragment
|
||||||
|
{
|
||||||
|
float4 position [[position]];
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex Vertex2Fragment VertexMain(
|
||||||
|
VertexInput in [[stage_in]],
|
||||||
|
constant VertexUniform& u [[buffer(0)]])
|
||||||
|
{
|
||||||
|
Vertex2Fragment out;
|
||||||
|
out.position = u.viewproj * float4(in.position, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 FragmentMain(Vertex2Fragment in [[stage_in]])
|
||||||
|
{
|
||||||
|
return half4(1.0);
|
||||||
|
}
|
||||||
39
src/shaders/lesson3.metal
Normal file
39
src/shaders/lesson3.metal
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
struct VertexInput
|
||||||
|
{
|
||||||
|
float3 position [[attribute(0)]];
|
||||||
|
float4 color [[attribute(1)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexUniform
|
||||||
|
{
|
||||||
|
metal::float4x4 viewproj;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex2Fragment
|
||||||
|
{
|
||||||
|
float4 position [[position]];
|
||||||
|
half4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex Vertex2Fragment VertexMain(
|
||||||
|
VertexInput in [[stage_in]],
|
||||||
|
constant VertexUniform& u [[buffer(0)]])
|
||||||
|
{
|
||||||
|
Vertex2Fragment out;
|
||||||
|
out.position = u.viewproj * float4(in.position, 1.0);
|
||||||
|
out.color = half4(in.color);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 FragmentMain(Vertex2Fragment in [[stage_in]])
|
||||||
|
{
|
||||||
|
return in.color;
|
||||||
|
}
|
||||||
45
src/shaders/lesson6.metal
Normal file
45
src/shaders/lesson6.metal
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
struct VertexInput
|
||||||
|
{
|
||||||
|
float3 position [[attribute(0)]];
|
||||||
|
float2 texcoord [[attribute(1)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexUniform
|
||||||
|
{
|
||||||
|
metal::float4x4 viewproj;
|
||||||
|
float4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex2Fragment
|
||||||
|
{
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 texcoord;
|
||||||
|
half4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex Vertex2Fragment VertexMain(
|
||||||
|
VertexInput in [[stage_in]],
|
||||||
|
constant VertexUniform& u [[buffer(0)]])
|
||||||
|
{
|
||||||
|
Vertex2Fragment out;
|
||||||
|
out.position = u.viewproj * float4(in.position, 1.0);
|
||||||
|
out.texcoord = in.texcoord;
|
||||||
|
out.color = half4(u.color);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 FragmentMain(
|
||||||
|
Vertex2Fragment in [[stage_in]],
|
||||||
|
metal::texture2d<half, metal::access::sample> texture [[texture(0)]],
|
||||||
|
metal::sampler sampler [[sampler(0)]])
|
||||||
|
{
|
||||||
|
return in.color * texture.sample(sampler, in.texcoord);
|
||||||
|
}
|
||||||
65
src/shaders/lesson7.metal
Normal file
65
src/shaders/lesson7.metal
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
struct VertexInput
|
||||||
|
{
|
||||||
|
float3 position [[attribute(0)]];
|
||||||
|
float2 texcoord [[attribute(1)]];
|
||||||
|
float3 normal [[attribute(2)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexUniform
|
||||||
|
{
|
||||||
|
metal::float4x4 modelView;
|
||||||
|
metal::float4x4 projection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Light
|
||||||
|
{
|
||||||
|
float4 ambient;
|
||||||
|
float4 diffuse;
|
||||||
|
float4 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex2Fragment
|
||||||
|
{
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 texcoord;
|
||||||
|
half4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex Vertex2Fragment VertexMain(
|
||||||
|
VertexInput in [[stage_in]],
|
||||||
|
constant VertexUniform& u [[buffer(0)]],
|
||||||
|
constant Light& light [[buffer(1)]])
|
||||||
|
{
|
||||||
|
const auto position = u.modelView * float4(in.position, 1.0);
|
||||||
|
const auto normal = metal::normalize(u.modelView * float4(in.normal, 0.0)).xyz;
|
||||||
|
|
||||||
|
const auto lightVec = light.position.xyz - position.xyz;
|
||||||
|
const auto lightDist2 = metal::length_squared(lightVec);
|
||||||
|
const auto dir = metal::rsqrt(lightDist2) * lightVec;
|
||||||
|
const auto lambert = metal::max(0.0, metal::dot(normal, dir));
|
||||||
|
|
||||||
|
const auto ambient = 0.04 + 0.2 * half3(light.ambient.rgb);
|
||||||
|
const auto diffuse = 0.8 * half3(light.diffuse.rgb);
|
||||||
|
|
||||||
|
Vertex2Fragment out;
|
||||||
|
out.position = u.projection * position;
|
||||||
|
out.texcoord = in.texcoord;
|
||||||
|
out.color = half4(ambient + lambert * diffuse, 1.0);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 FragmentMain(
|
||||||
|
Vertex2Fragment in [[stage_in]],
|
||||||
|
metal::texture2d<half, metal::access::sample> texture [[texture(0)]],
|
||||||
|
metal::sampler sampler [[sampler(0)]])
|
||||||
|
{
|
||||||
|
return in.color * texture.sample(sampler, in.texcoord);
|
||||||
|
}
|
||||||
52
src/shaders/lesson9.metal
Normal file
52
src/shaders/lesson9.metal
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
struct VertexInput
|
||||||
|
{
|
||||||
|
float3 position [[attribute(0)]];
|
||||||
|
float2 texcoord [[attribute(1)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexInstance
|
||||||
|
{
|
||||||
|
metal::float4x4 model;
|
||||||
|
float4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexUniform
|
||||||
|
{
|
||||||
|
metal::float4x4 projection;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Vertex2Fragment
|
||||||
|
{
|
||||||
|
float4 position [[position]];
|
||||||
|
float2 texcoord;
|
||||||
|
half4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex Vertex2Fragment VertexMain(
|
||||||
|
VertexInput in [[stage_in]],
|
||||||
|
const device VertexInstance* instance [[buffer(1)]],
|
||||||
|
constant VertexUniform& u [[buffer(0)]],
|
||||||
|
const uint instanceIdx [[instance_id]])
|
||||||
|
{
|
||||||
|
Vertex2Fragment out;
|
||||||
|
out.position = u.projection * instance[instanceIdx].model * float4(in.position, 1.0);
|
||||||
|
out.texcoord = in.texcoord;
|
||||||
|
out.color = half4(instance[instanceIdx].color);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment half4 FragmentMain(
|
||||||
|
Vertex2Fragment in [[stage_in]],
|
||||||
|
metal::texture2d<half, metal::access::sample> texture [[texture(0)]],
|
||||||
|
metal::sampler sampler [[sampler(0)]])
|
||||||
|
{
|
||||||
|
return in.color * texture.sample(sampler, in.texcoord);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user