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