diff options
45 files changed, 2498 insertions, 98 deletions
diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..71d4c7b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +* text=auto +*.spv binary +*.hdr binary +*.ttf binary +*.png binary +*.jpg binary +*.glsl text eol=lf +*.slang text eol=lf +*.sh text eol=lf +*.lua text eol=lf @@ -28,5 +28,7 @@ Makefile *.vcxproj.* tools/slang/ -assets/shaders/generated/ +assets/shaders/generated/*.metal +assets/shaders/generated/.err +assets/shaders/generated/.stage.glsl exports/ @@ -21,6 +21,28 @@ the curvature of spacetime changes the direction of each ray as it passes around ## rendering for each pixel, donut generates a ray from the camera using its position, orientation, field of view, and aspect ratio. the ray is then converted into the coordinates used by the geodesic integrator and propagated through the scene. during integration, the renderer checks for intersections with the black hole, accretion disk, and other scene objects. rays that escape are sampled against the background environment. the accretion disk is a thin, opaque, self-luminous surface in the equatorial plane, with a novikov–thorne temperature profile that is hottest just outside the inner edge and cools outward, coloured as a redshifted blackbody; an optional turbulence overlay modulates its brightness. its gas orbits at relativistic keplerian speed (reaching half the speed of light at the innermost stable orbit), so the disk is doppler-brightened on the side rotating toward the camera. since the disk is viewed through curved spacetime, different parts of it can reach the camera along multiple paths around the black hole. the resulting image includes gravitational lensing, gravitational redshift, and doppler shifting from the rotating disk. the final pixel color is determined from the ray's path, its intersection with the scene, and the relativistic effects accumulated along the way. the entire process runs on the gpu as a full-screen fragment shader, tracing every pixel's ray in parallel. +## building +the libraries in `ext/` are git submodules, so clone with `--recursive` (or run `git submodule update --init` in an existing clone). the shaders are prebuilt in `assets/shaders/generated/` and committed, so a plain build doesn't need the slang toolchain. + +**macos (apple silicon)** + +```bash +brew install premake molten-vk vulkan-loader vulkan-headers spirv-cross +tools/fetch-slang.sh +premake5 gmake && make config=debug donut +./bin/debug-macosx/donut +``` + +on macos `make` also regenerates the shaders from `assets/shaders/*.slang`, which is why it wants slang and spirv-cross. + +**windows (x64, visual studio 2022)** + +1. install the [lunarg vulkan sdk](https://vulkan.lunarg.com/sdk/home). its installer sets `VULKAN_SDK`, which the build reads, so open a fresh terminal afterwards. without the sdk the build still works, it just leaves the vulkan backend out and runs on opengl. +2. run `premake5 vs2022` from the repository root ([premake](https://premake.github.io/) is a single exe). +3. open `donut.sln`, pick `debug` and `x64`, and build. f5 runs the app from the repository root; the exe also finds `assets/` on its own when launched from `bin\debug-windows\`. + +**picking a backend.** `config/settings.toml` is created on first run, and `render_api = "OpenGL"` or `"Vulkan"` picks the backend (also under settings → renderer, applied on restart). if vulkan can't start on a machine, donut falls back to opengl and says why in `logs/donut.log`. + ## documentation for a deeper explanation of how donut works, see the [`docs/`](docs/) folder: - [docs/physics.md](docs/physics.md) — the physics and mathematics: null geodesics and the equations of motion, the numerical integrator, the event horizon / photon sphere / isco, the novikov–thorne accretion disk, gravitational + doppler redshift and relativistic beaming, and the observable quantities the renderer can measure. diff --git a/assets/shaders/equirect_to_cubemap.slang b/assets/shaders/equirect_to_cubemap.slang index 5b4caff..9316646 100644 --- a/assets/shaders/equirect_to_cubemap.slang +++ b/assets/shaders/equirect_to_cubemap.slang @@ -1,8 +1,7 @@ -// equirectangular HDRI -> cubemap face projection. NOTE: the OpenGL path uses the -// hand-written assets/shaders/EquirectToCubemap.glsl instead; this Slang version -// feeds the Vulkan cubemap bake (vulkan_cubemap.cpp), which binds the view / -// projection as a $Globals UBO. kept as loose `uniform` globals (not a -// ConstantBuffer) so both paths bind them the same way. +// equirectangular HDRI -> cubemap face projection. both bakes use it: the Vulkan +// one (vulkan_cubemap.cpp) binds view / projection as a $Globals UBO, the OpenGL +// one (texture.cpp) sets the flattened u_projection / u_view uniforms. kept as +// loose `uniform` globals (not a ConstantBuffer) so both paths bind them the same way. uniform float4x4 u_projection; uniform float4x4 u_view; diff --git a/assets/shaders/generated/equirect_to_cubemap.fragmentMain.spv b/assets/shaders/generated/equirect_to_cubemap.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..28ed588 --- /dev/null +++ b/assets/shaders/generated/equirect_to_cubemap.fragmentMain.spv diff --git a/assets/shaders/generated/equirect_to_cubemap.glsl b/assets/shaders/generated/equirect_to_cubemap.glsl new file mode 100644 index 0000000..7e1d521 --- /dev/null +++ b/assets/shaders/generated/equirect_to_cubemap.glsl @@ -0,0 +1,33 @@ +#type vertex +#version 410 + +uniform mat4 u_projection; +uniform mat4 u_view; + +layout(location = 0) in vec3 input_position; +layout(location = 0) out vec3 _v0; + +mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; } + +void main() +{ + gl_Position = (vec4(input_position, 1.0) * spvWorkaroundRowMajor(u_view)) * spvWorkaroundRowMajor(u_projection); + _v0 = input_position; +} + + +#type fragment +#version 410 + +uniform sampler2D u_EquirectangularMap; + +layout(location = 0) in vec3 _v0; +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + vec3 _10 = normalize(_v0); + entryPointParam_fragmentMain = vec4(texture(u_EquirectangularMap, (vec2(atan(_10.z, _10.x), asin(_10.y)) * vec2(0.159099996089935302734375, 0.3183000087738037109375)) + vec2(0.5)).xyz, 1.0); +} + + diff --git a/assets/shaders/generated/equirect_to_cubemap.vertexMain.spv b/assets/shaders/generated/equirect_to_cubemap.vertexMain.spv Binary files differnew file mode 100644 index 0000000..52ea1b8 --- /dev/null +++ b/assets/shaders/generated/equirect_to_cubemap.vertexMain.spv diff --git a/assets/shaders/generated/geodesic.fragmentMain.spv b/assets/shaders/generated/geodesic.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..14ac60f --- /dev/null +++ b/assets/shaders/generated/geodesic.fragmentMain.spv diff --git a/assets/shaders/generated/geodesic.glsl b/assets/shaders/generated/geodesic.glsl new file mode 100644 index 0000000..5c5e069 --- /dev/null +++ b/assets/shaders/generated/geodesic.glsl @@ -0,0 +1,1854 @@ +#type vertex +#version 410 + +layout(location = 0) in vec2 input_position; +layout(location = 1) in vec2 input_texCoord; +layout(location = 0) out vec2 _v0; + +void main() +{ + gl_Position = vec4(input_position, 0.0, 1.0); + _v0 = input_texCoord; +} + + +#type fragment +#version 410 + +struct _Array_vector_float_4_16 +{ + vec4 data[16]; +}; + +struct _Array_float16 +{ + float data[16]; +}; + +const vec2 _1399[4] = vec2[](vec2(0.125, 0.375), vec2(0.375, -0.125), vec2(-0.125, -0.375), vec2(-0.375, 0.125)); + +layout(std140) uniform Camera +{ + vec3 u_cam_pos; + float _pad0; + vec3 u_cam_right; + float _pad1; + vec3 u_cam_up; + float _pad2; + vec3 u_cam_forward; + float _pad3; + float u_tan_half_fov; + float u_aspect; + int u_moving; + int u_output_channel; + int u_raw_output; +} cam; + +layout(std140) uniform Simulation +{ + int u_max_steps_moving; + int u_max_steps_static; + float u_early_exit_distance; + float u_time; +} sim; + +layout(std140) uniform Disk +{ + float u_inner_radius; + float u_outer_radius; + float u_turbulence; + float u_thickness; + float u_brightness; + float u_temperature; +} disk; + +layout(std140) uniform Objects +{ + int u_num_objects; + _Array_vector_float_4_16 u_obj_pos_radius; + _Array_vector_float_4_16 u_obj_color; + _Array_float16 u_mass; +} obj; + +uniform samplerCube u_HDRIEnvironment; + +layout(location = 0) in vec2 _v0; +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + bool _4918 = false; + bool _4023 = false; + bool _3148 = false; + bool _2258 = false; + do + { + if (cam.u_output_channel != 0) + { + float g = 0.0; + float Temit = 0.0; + bool hitDisk = false; + vec3 _1488 = normalize(((cam.u_cam_right * ((((2.0 * _v0.x) - 1.0) * cam.u_aspect) * cam.u_tan_half_fov)) - (cam.u_cam_up * ((1.0 - (2.0 * _v0.y)) * cam.u_tan_half_fov))) + cam.u_cam_forward); + float _1693 = length(cam.u_cam_pos); + float _1696 = acos(cam.u_cam_pos.z / _1693); + float _1698 = atan(cam.u_cam_pos.y, cam.u_cam_pos.x); + float _1699 = _1488.x; + float _1700 = _1488.y; + float _1701 = _1488.z; + float _1714 = (((sin(_1696) * cos(_1698)) * _1699) + ((sin(_1696) * sin(_1698)) * _1700)) + (cos(_1696) * _1701); + float _1728 = ((((cos(_1696) * cos(_1698)) * _1699) + ((cos(_1696) * sin(_1698)) * _1700)) - (sin(_1696) * _1701)) / _1693; + float _1738 = (((-sin(_1698)) * _1699) + (cos(_1698) * _1700)) / (_1693 * sin(_1696)); + float _1745 = 1.0 - (12689999872.0 / _1693); + float _5541 = cam.u_cam_pos.x; + float _5542 = cam.u_cam_pos.y; + float _5543 = cam.u_cam_pos.z; + float _5544 = _1693; + float _5545 = _1696; + float _5546 = _1698; + float _5547 = _1714; + float _5548 = _1728; + float _5549 = _1738; + float _5550 = _1745 * sqrt(((_1714 * _1714) / _1745) + ((_1693 * _1693) * ((_1728 * _1728) + (((sin(_1696) * sin(_1696)) * _1738) * _1738)))); + int _1450; + if (cam.u_moving != 0) + { + _1450 = sim.u_max_steps_moving; + } + else + { + _1450 = sim.u_max_steps_static; + } + if (_1450 <= 0) + { + if (cam.u_moving != 0) + { + _1450 = 12000; + } + else + { + _1450 = 8000; + } + } + float _1451; + if (sim.u_early_exit_distance > 0.0) + { + _1451 = sim.u_early_exit_distance; + } + else + { + _1451 = 1999999991808.0; + } + int _1452 = 0; + bool _1453; + bool _1454; + bool _1455; + vec3 _1456; + bool _1460; + bool _1766; + float _1767; + vec3 _1805; + vec3 _1915; + bool _1916; + float _1917; + vec3 _2019; + float _2020; + bool _2259; + for (;;) + { + if (!(_1452 < _1450)) + { + _1453 = false; + _1456 = vec3(0.0); + _1454 = false; + _1455 = false; + break; + } + if (_5544 <= 12689999872.0) + { + _1453 = false; + _1456 = vec3(0.0); + _1454 = true; + _1455 = false; + break; + } + if (_5544 > _1451) + { + _1453 = true; + } + else + { + _1453 = _5544 > 1000000015047466219876688855040.0; + } + if (_1453) + { + _1453 = false; + _1456 = vec3(0.0); + _1454 = false; + _1455 = false; + break; + } + float _1547 = _5541; + float _1549 = _5542; + float _1551 = _5543; + vec3 _1552 = vec3(_1547, _1549, _1551); + float _5614 = _1549; + float _1774 = 0.0199999995529651641845703125 * max(_5544 - 19035000832.0, 0.0); + if (length(vec2(_1547, _1551)) < (disk.u_outer_radius * 3.0)) + { + _1766 = abs(_5614) < (disk.u_thickness * 8.0); + } + else + { + _1766 = false; + } + if (_1766) + { + _1767 = min(_1774, disk.u_thickness); + } + else + { + _1767 = _1774; + } + float _1803 = clamp(_1767, 1000000.0, 20000000000.0); + float _5607 = _5549; + float _5606 = _5548; + float _5605 = _5547; + float _5604 = _5546; + float _5603 = _5545; + float _5602 = _5544; + float _1871 = 1.0 - (12689999872.0 / _5602); + float _1874 = _5550 / _1871; + vec3 _1875 = vec3(_5605, _5606, _5607); + float _1878 = (2.0 * _5602) * _5602; + float _1890 = sin(_5603); + vec3 _5640 = _1805; + _5640.x = ((((((-12689999872.0) / _1878) * _1871) * _1874) * _1874) + (((12689999872.0 / (_1878 * _1871)) * _5605) * _5605)) + (_5602 * ((_5606 * _5606) + (((_1890 * _1890) * _5607) * _5607))); + float _1898 = (-2.0) * _5605; + float _1901 = cos(_5603); + _5640.y = ((_1898 * _5606) / _5602) + (((_1890 * _1901) * _5607) * _5607); + _5640.z = ((_1898 * _5607) / _5602) - ((((2.0 * _1901) / _1890) * _5606) * _5607); + _1805 = _5640; + float _1815 = _5602 + (_1803 * _1875.x); + _5544 = _1815; + float _1821 = _5603 + (_1803 * _1875.y); + _5545 = _1821; + float _1827 = _5604 + (_1803 * _1875.z); + _5546 = _1827; + _5547 = _5605 + (_1803 * _5640.x); + _5548 = _5606 + (_1803 * _5640.y); + _5549 = _5607 + (_1803 * _5640.z); + float _1850 = (_1815 * sin(_1821)) * cos(_1827); + _5541 = _1850; + float _1855 = (_1815 * sin(_1821)) * sin(_1827); + _5542 = _1855; + float _1858 = _1815 * cos(_1821); + _5543 = _1858; + vec3 _1559 = vec3(_1850, _1855, _1858); + bool _1563 = (_1549 * _1855) < 0.0; + if (_1563) + { + _1454 = true; + } + else + { + _1454 = abs(_1855) <= disk.u_thickness; + } + if (_1454) + { + if (_1563) + { + _1456 = mix(_1552, _1559, vec3(_1549 / (_1549 - _1855))); + } + else + { + _1456 = _1559; + } + float _1583 = length(vec2(_1456.xz)); + if (_1583 >= max(disk.u_inner_radius, 38070001664.0)) + { + _1455 = _1583 <= disk.u_outer_radius; + } + else + { + _1455 = false; + } + if (_1455) + { + do + { + g = 0.0; + Temit = 0.0; + float _1924 = length(vec2(_1456.xz)); + float _1927 = max(disk.u_inner_radius, 38070001664.0); + if (_1924 < _1927) + { + _1916 = true; + } + else + { + _1916 = _1924 > disk.u_outer_radius; + } + if (_1916) + { + _1915 = vec3(0.0); + break; + } + float _1938 = _1924 / _1927; + float _1947 = pow(max((1.0 - sqrt(1.0 / _1938)) / ((_1938 * _1938) * _1938), 0.0) * 17.5746936798095703125, 0.25); + float _1950 = disk.u_temperature * _1947; + float _1969 = sqrt(max(1.0 - (19035000832.0 / _1924), 0.0)) / max(1.0 - dot(normalize(cross(vec3(0.0, 1.0, 0.0), normalize(vec3(_1456.x, 0.0, _1456.z)))) * sqrt(6344999936.0 / max(_1924 - 12689999872.0, 1.0)), -normalize(_1559 - _1552)), 0.001000000047497451305389404296875); + g = _1969; + Temit = _1950; + float _2024 = clamp(_1969 * _1950, 1000.0, 40000.0) * 0.00999999977648258209228515625; + bool _2026 = _2024 <= 66.0; + if (_2026) + { + _2020 = 1.0; + } + else + { + _2020 = clamp(1.29293620586395263671875 * pow(_2024 - 60.0, -0.133204758167266845703125), 0.0, 1.0); + } + vec3 _5646 = _2019; + _5646.x = _2020; + _2019 = _5646; + if (_2026) + { + _2020 = clamp((0.390081584453582763671875 * log(_2024)) - 0.6318414211273193359375, 0.0, 1.0); + } + else + { + _2020 = clamp(1.129890918731689453125 * pow(_2024 - 60.0, -0.075514845550060272216796875), 0.0, 1.0); + } + vec3 _5648 = _2019; + _5648.y = _2020; + _2019 = _5648; + if (_2024 >= 66.0) + { + _2020 = 1.0; + } + else + { + if (_2024 <= 19.0) + { + _2020 = 0.0; + } + else + { + _2020 = clamp((0.54320681095123291015625 * log(_2024 - 10.0)) - 1.19625413417816162109375, 0.0, 1.0); + } + } + vec3 _5650 = _2019; + _5650.z = _2020; + _2019 = _5650; + float _1981 = (pow(_1947, 2.0) * pow(_1969, 4.0)) * (smoothstep(_1927, _1927 * 1.12000000476837158203125, _1924) * (1.0 - smoothstep(disk.u_outer_radius * 0.87999999523162841796875, disk.u_outer_radius, _1924))); + if (disk.u_turbulence > 0.0) + { + float _1991 = (sim.u_time * 0.300000011920928955078125) / sqrt(_1938); + float _1992 = cos(_1991); + float _1994 = sin(_1991); + vec3 _2064 = (vec3((_1456.x * _1992) - (_1456.z * _1994), 0.0, (_1456.x * _1994) + (_1456.z * _1992)) * 1.0000000133514319600180897396058e-10) * 3.0; + int _2065 = 0; + float _2066 = 0.5; + float _2067 = 0.0; + for (;;) + { + if (!(_2065 < 3)) + { + break; + } + vec3 _2098 = floor(_2064); + vec3 _2099 = fract(_2064); + vec3 _2104 = (_2099 * _2099) * (vec3(3.0) - (_2099 * 2.0)); + vec3 _2133 = fract(_2098 * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2139 = _2133 + vec3(dot(_2133, _2133.yxz + vec3(33.3300018310546875))); + vec3 _2149 = fract((_2098 + vec3(1.0, 0.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2155 = _2149 + vec3(dot(_2149, _2149.yxz + vec3(33.3300018310546875))); + vec3 _2165 = fract((_2098 + vec3(0.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2171 = _2165 + vec3(dot(_2165, _2165.yxz + vec3(33.3300018310546875))); + vec3 _2181 = fract((_2098 + vec3(1.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2187 = _2181 + vec3(dot(_2181, _2181.yxz + vec3(33.3300018310546875))); + vec3 _2197 = fract((_2098 + vec3(0.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2203 = _2197 + vec3(dot(_2197, _2197.yxz + vec3(33.3300018310546875))); + vec3 _2213 = fract((_2098 + vec3(1.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2219 = _2213 + vec3(dot(_2213, _2213.yxz + vec3(33.3300018310546875))); + vec3 _2229 = fract((_2098 + vec3(0.0, 1.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2235 = _2229 + vec3(dot(_2229, _2229.yxz + vec3(33.3300018310546875))); + vec3 _2245 = fract((_2098 + vec3(1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _2251 = _2245 + vec3(dot(_2245, _2245.yxz + vec3(33.3300018310546875))); + float _2120 = _2104.x; + float _2123 = _2104.y; + float _2080 = _2066; + float _2081 = _2080 * mix(mix(mix(fract((_2139.x + _2139.y) * _2139.z), fract((_2155.x + _2155.y) * _2155.z), _2120), mix(fract((_2171.x + _2171.y) * _2171.z), fract((_2187.x + _2187.y) * _2187.z), _2120), _2123), mix(mix(fract((_2203.x + _2203.y) * _2203.z), fract((_2219.x + _2219.y) * _2219.z), _2120), mix(fract((_2235.x + _2235.y) * _2235.z), fract((_2251.x + _2251.y) * _2251.z), _2120), _2123), _2104.z); + _2064 = (_2064 * 2.0) + vec3(100.0, 200.0, 300.0); + _2065++; + _2066 = _2080 * 0.5; + _2067 += _2081; + continue; + } + _1917 = _1981 * max(1.0 + (disk.u_turbulence * (_2067 - 0.5)), 0.0); + } + else + { + _1917 = _1981; + } + _1915 = (_5650 * _1917) * max(disk.u_brightness, 0.0); + break; + } while(false); + hitDisk = true; + _1453 = true; + _1456 = _1915; + _1454 = false; + _1455 = false; + break; + } + } + if ((_1452 - 5 * (_1452 / 5)) == 0) + { + float _5621 = _5541; + float _5622 = _5542; + float _5623 = _5543; + _2258 = false; + do + { + vec3 _2270 = vec3(_5621, _5622, _5623); + int _2260 = 0; + for (;;) + { + bool _2273_ladder_break = false; + do + { + if (!(_2260 < obj.u_num_objects)) + { + _2273_ladder_break = true; + break; + } + vec3 _2289 = _2270 - obj.u_obj_pos_radius.data[_2260].xyz; + float _2290 = dot(_2289, _2289); + float _2291 = obj.u_obj_pos_radius.data[_2260].w * obj.u_obj_pos_radius.data[_2260].w; + if (_2290 > (_2291 * 4.0)) + { + break; + } + if (_2290 <= _2291) + { + _2258 = true; + _2259 = true; + _2273_ladder_break = true; + break; + } + break; + } while(false); + if (_2273_ladder_break) + { + break; + } + _2260++; + continue; + } + if (_2258) + { + break; + } + _2258 = true; + _2259 = false; + break; + } while(false); + _1455 = _2259; + } + else + { + _1455 = false; + } + if (_1455) + { + _1453 = false; + _1456 = vec3(0.0); + _1454 = false; + _1455 = true; + break; + } + if (_5547 > 0.0) + { + _1460 = _5544 > 634500022272.0; + } + else + { + _1460 = false; + } + if (_1460) + { + _1453 = false; + _1456 = vec3(0.0); + _1454 = false; + _1455 = false; + break; + } + _1452++; + continue; + } + float value; + float valid; + if (cam.u_output_channel == 3) + { + value = length(cross(cam.u_cam_pos, normalize(((cam.u_cam_right * ((((2.0 * _v0.x) - 1.0) * cam.u_aspect) * cam.u_tan_half_fov)) - (cam.u_cam_up * ((1.0 - (2.0 * _v0.y)) * cam.u_tan_half_fov))) + cam.u_cam_forward))) * 7.8802207814643310257451958023012e-11; + valid = 1.0; + } + else + { + if (hitDisk) + { + value = 1.0; + } + else + { + value = 0.0; + } + if (cam.u_output_channel == 1) + { + valid = g; + } + else + { + valid = Temit; + } + float _1258 = value; + value = valid; + valid = _1258; + } + if (cam.u_raw_output != 0) + { + entryPointParam_fragmentMain = vec4(value, value, value, valid); + break; + } + if (valid < 0.5) + { + entryPointParam_fragmentMain = vec4(0.0, 0.0, 0.0, 1.0); + break; + } + float norm; + if (cam.u_output_channel == 1) + { + norm = value * 0.666666686534881591796875; + } + else + { + if (cam.u_output_channel == 2) + { + value *= 6.6666667407844215631484985351562e-05; + } + else + { + value *= 0.0333333350718021392822265625; + } + norm = value; + } + float _2326 = 4.0 * clamp(norm, 0.0, 1.0); + entryPointParam_fragmentMain = vec4(clamp(vec3(1.5 - abs(_2326 - 3.0), 1.5 - abs(_2326 - 2.0), 1.5 - abs(_2326 - 1.0)), vec3(0.0), vec3(1.0)), 1.0); + break; + } + if (cam.u_raw_output != 0) + { + vec3 _2378 = normalize(((cam.u_cam_right * ((((2.0 * _v0.x) - 1.0) * cam.u_aspect) * cam.u_tan_half_fov)) - (cam.u_cam_up * ((1.0 - (2.0 * _v0.y)) * cam.u_tan_half_fov))) + cam.u_cam_forward); + float _2583 = length(cam.u_cam_pos); + float _2586 = acos(cam.u_cam_pos.z / _2583); + float _2588 = atan(cam.u_cam_pos.y, cam.u_cam_pos.x); + float _2589 = _2378.x; + float _2590 = _2378.y; + float _2591 = _2378.z; + float _2604 = (((sin(_2586) * cos(_2588)) * _2589) + ((sin(_2586) * sin(_2588)) * _2590)) + (cos(_2586) * _2591); + float _2618 = ((((cos(_2586) * cos(_2588)) * _2589) + ((cos(_2586) * sin(_2588)) * _2590)) - (sin(_2586) * _2591)) / _2583; + float _2628 = (((-sin(_2588)) * _2589) + (cos(_2588) * _2590)) / (_2583 * sin(_2586)); + float _2635 = 1.0 - (12689999872.0 / _2583); + float _5383 = cam.u_cam_pos.x; + float _5384 = cam.u_cam_pos.y; + float _5385 = cam.u_cam_pos.z; + float _5386 = _2583; + float _5387 = _2586; + float _5388 = _2588; + float _5389 = _2604; + float _5390 = _2618; + float _5391 = _2628; + float _5392 = _2635 * sqrt(((_2604 * _2604) / _2635) + ((_2583 * _2583) * ((_2618 * _2618) + (((sin(_2586) * sin(_2586)) * _2628) * _2628)))); + vec4 _5453 = vec4(0.0); + vec3 _5454 = vec3(0.0); + int _2340; + if (cam.u_moving != 0) + { + _2340 = sim.u_max_steps_moving; + } + else + { + _2340 = sim.u_max_steps_static; + } + if (_2340 <= 0) + { + if (cam.u_moving != 0) + { + _2340 = 12000; + } + else + { + _2340 = 8000; + } + } + float _2341; + if (sim.u_early_exit_distance > 0.0) + { + _2341 = sim.u_early_exit_distance; + } + else + { + _2341 = 1999999991808.0; + } + int _2342 = 0; + bool _2343; + bool _2344; + bool _2345; + vec3 _2346; + bool _2350; + bool _2656; + float _2657; + vec3 _2695; + vec3 _2805; + bool _2806; + float _2807; + vec3 _2909; + float _2910; + bool _3149; + for (;;) + { + if (!(_2342 < _2340)) + { + _2343 = false; + _2346 = vec3(0.0); + _2344 = false; + _2345 = false; + break; + } + if (_5386 <= 12689999872.0) + { + _2343 = false; + _2346 = vec3(0.0); + _2344 = true; + _2345 = false; + break; + } + if (_5386 > _2341) + { + _2343 = true; + } + else + { + _2343 = _5386 > 1000000015047466219876688855040.0; + } + if (_2343) + { + _2343 = false; + _2346 = vec3(0.0); + _2344 = false; + _2345 = false; + break; + } + float _2437 = _5383; + float _2439 = _5384; + float _2441 = _5385; + vec3 _2442 = vec3(_2437, _2439, _2441); + float _5459 = _2439; + float _2664 = 0.0199999995529651641845703125 * max(_5386 - 19035000832.0, 0.0); + if (length(vec2(_2437, _2441)) < (disk.u_outer_radius * 3.0)) + { + _2656 = abs(_5459) < (disk.u_thickness * 8.0); + } + else + { + _2656 = false; + } + if (_2656) + { + _2657 = min(_2664, disk.u_thickness); + } + else + { + _2657 = _2664; + } + float _2693 = clamp(_2657, 1000000.0, 20000000000.0); + float _5449 = _5391; + float _5448 = _5390; + float _5447 = _5389; + float _5446 = _5388; + float _5445 = _5387; + float _5444 = _5386; + float _2761 = 1.0 - (12689999872.0 / _5444); + float _2764 = _5392 / _2761; + vec3 _2765 = vec3(_5447, _5448, _5449); + float _2768 = (2.0 * _5444) * _5444; + float _2780 = sin(_5445); + vec3 _5652 = _2695; + _5652.x = ((((((-12689999872.0) / _2768) * _2761) * _2764) * _2764) + (((12689999872.0 / (_2768 * _2761)) * _5447) * _5447)) + (_5444 * ((_5448 * _5448) + (((_2780 * _2780) * _5449) * _5449))); + float _2788 = (-2.0) * _5447; + float _2791 = cos(_5445); + _5652.y = ((_2788 * _5448) / _5444) + (((_2780 * _2791) * _5449) * _5449); + _5652.z = ((_2788 * _5449) / _5444) - ((((2.0 * _2791) / _2780) * _5448) * _5449); + _2695 = _5652; + float _2705 = _5444 + (_2693 * _2765.x); + _5386 = _2705; + float _2711 = _5445 + (_2693 * _2765.y); + _5387 = _2711; + float _2717 = _5446 + (_2693 * _2765.z); + _5388 = _2717; + _5389 = _5447 + (_2693 * _5652.x); + _5390 = _5448 + (_2693 * _5652.y); + _5391 = _5449 + (_2693 * _5652.z); + float _2740 = (_2705 * sin(_2711)) * cos(_2717); + _5383 = _2740; + float _2745 = (_2705 * sin(_2711)) * sin(_2717); + _5384 = _2745; + float _2748 = _2705 * cos(_2711); + _5385 = _2748; + vec3 _2449 = vec3(_2740, _2745, _2748); + bool _2453 = (_2439 * _2745) < 0.0; + if (_2453) + { + _2344 = true; + } + else + { + _2344 = abs(_2745) <= disk.u_thickness; + } + if (_2344) + { + if (_2453) + { + _2346 = mix(_2442, _2449, vec3(_2439 / (_2439 - _2745))); + } + else + { + _2346 = _2449; + } + float _2473 = length(vec2(_2346.xz)); + if (_2473 >= max(disk.u_inner_radius, 38070001664.0)) + { + _2345 = _2473 <= disk.u_outer_radius; + } + else + { + _2345 = false; + } + if (_2345) + { + do + { + float _2814 = length(vec2(_2346.xz)); + float _2817 = max(disk.u_inner_radius, 38070001664.0); + if (_2814 < _2817) + { + _2806 = true; + } + else + { + _2806 = _2814 > disk.u_outer_radius; + } + if (_2806) + { + _2805 = vec3(0.0); + break; + } + float _2828 = _2814 / _2817; + float _2837 = pow(max((1.0 - sqrt(1.0 / _2828)) / ((_2828 * _2828) * _2828), 0.0) * 17.5746936798095703125, 0.25); + float _2859 = sqrt(max(1.0 - (19035000832.0 / _2814), 0.0)) / max(1.0 - dot(normalize(cross(vec3(0.0, 1.0, 0.0), normalize(vec3(_2346.x, 0.0, _2346.z)))) * sqrt(6344999936.0 / max(_2814 - 12689999872.0, 1.0)), -normalize(_2449 - _2442)), 0.001000000047497451305389404296875); + float _2914 = clamp(_2859 * (disk.u_temperature * _2837), 1000.0, 40000.0) * 0.00999999977648258209228515625; + bool _2916 = _2914 <= 66.0; + if (_2916) + { + _2910 = 1.0; + } + else + { + _2910 = clamp(1.29293620586395263671875 * pow(_2914 - 60.0, -0.133204758167266845703125), 0.0, 1.0); + } + vec3 _5658 = _2909; + _5658.x = _2910; + _2909 = _5658; + if (_2916) + { + _2910 = clamp((0.390081584453582763671875 * log(_2914)) - 0.6318414211273193359375, 0.0, 1.0); + } + else + { + _2910 = clamp(1.129890918731689453125 * pow(_2914 - 60.0, -0.075514845550060272216796875), 0.0, 1.0); + } + vec3 _5660 = _2909; + _5660.y = _2910; + _2909 = _5660; + if (_2914 >= 66.0) + { + _2910 = 1.0; + } + else + { + if (_2914 <= 19.0) + { + _2910 = 0.0; + } + else + { + _2910 = clamp((0.54320681095123291015625 * log(_2914 - 10.0)) - 1.19625413417816162109375, 0.0, 1.0); + } + } + vec3 _5662 = _2909; + _5662.z = _2910; + _2909 = _5662; + float _2871 = (pow(_2837, 2.0) * pow(_2859, 4.0)) * (smoothstep(_2817, _2817 * 1.12000000476837158203125, _2814) * (1.0 - smoothstep(disk.u_outer_radius * 0.87999999523162841796875, disk.u_outer_radius, _2814))); + if (disk.u_turbulence > 0.0) + { + float _2881 = (sim.u_time * 0.300000011920928955078125) / sqrt(_2828); + float _2882 = cos(_2881); + float _2884 = sin(_2881); + vec3 _2954 = (vec3((_2346.x * _2882) - (_2346.z * _2884), 0.0, (_2346.x * _2884) + (_2346.z * _2882)) * 1.0000000133514319600180897396058e-10) * 3.0; + int _2955 = 0; + float _2956 = 0.5; + float _2957 = 0.0; + for (;;) + { + if (!(_2955 < 3)) + { + break; + } + vec3 _2988 = floor(_2954); + vec3 _2989 = fract(_2954); + vec3 _2994 = (_2989 * _2989) * (vec3(3.0) - (_2989 * 2.0)); + vec3 _3023 = fract(_2988 * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3029 = _3023 + vec3(dot(_3023, _3023.yxz + vec3(33.3300018310546875))); + vec3 _3039 = fract((_2988 + vec3(1.0, 0.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3045 = _3039 + vec3(dot(_3039, _3039.yxz + vec3(33.3300018310546875))); + vec3 _3055 = fract((_2988 + vec3(0.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3061 = _3055 + vec3(dot(_3055, _3055.yxz + vec3(33.3300018310546875))); + vec3 _3071 = fract((_2988 + vec3(1.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3077 = _3071 + vec3(dot(_3071, _3071.yxz + vec3(33.3300018310546875))); + vec3 _3087 = fract((_2988 + vec3(0.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3093 = _3087 + vec3(dot(_3087, _3087.yxz + vec3(33.3300018310546875))); + vec3 _3103 = fract((_2988 + vec3(1.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3109 = _3103 + vec3(dot(_3103, _3103.yxz + vec3(33.3300018310546875))); + vec3 _3119 = fract((_2988 + vec3(0.0, 1.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3125 = _3119 + vec3(dot(_3119, _3119.yxz + vec3(33.3300018310546875))); + vec3 _3135 = fract((_2988 + vec3(1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3141 = _3135 + vec3(dot(_3135, _3135.yxz + vec3(33.3300018310546875))); + float _3010 = _2994.x; + float _3013 = _2994.y; + float _2970 = _2956; + float _2971 = _2970 * mix(mix(mix(fract((_3029.x + _3029.y) * _3029.z), fract((_3045.x + _3045.y) * _3045.z), _3010), mix(fract((_3061.x + _3061.y) * _3061.z), fract((_3077.x + _3077.y) * _3077.z), _3010), _3013), mix(mix(fract((_3093.x + _3093.y) * _3093.z), fract((_3109.x + _3109.y) * _3109.z), _3010), mix(fract((_3125.x + _3125.y) * _3125.z), fract((_3141.x + _3141.y) * _3141.z), _3010), _3013), _2994.z); + _2954 = (_2954 * 2.0) + vec3(100.0, 200.0, 300.0); + _2955++; + _2956 = _2970 * 0.5; + _2957 += _2971; + continue; + } + _2807 = _2871 * max(1.0 + (disk.u_turbulence * (_2957 - 0.5)), 0.0); + } + else + { + _2807 = _2871; + } + _2805 = (_5662 * _2807) * max(disk.u_brightness, 0.0); + break; + } while(false); + _2343 = true; + _2346 = _2805; + _2344 = false; + _2345 = false; + break; + } + } + if ((_2342 - 5 * (_2342 / 5)) == 0) + { + float _5466 = _5383; + float _5467 = _5384; + float _5468 = _5385; + _3148 = false; + do + { + vec3 _3160 = vec3(_5466, _5467, _5468); + int _3150 = 0; + for (;;) + { + bool _3163_ladder_break = false; + do + { + if (!(_3150 < obj.u_num_objects)) + { + _3163_ladder_break = true; + break; + } + vec3 _3179 = _3160 - obj.u_obj_pos_radius.data[_3150].xyz; + float _3180 = dot(_3179, _3179); + float _3181 = obj.u_obj_pos_radius.data[_3150].w * obj.u_obj_pos_radius.data[_3150].w; + if (_3180 > (_3181 * 4.0)) + { + break; + } + if (_3180 <= _3181) + { + _5453 = obj.u_obj_color.data[_3150]; + _5454 = obj.u_obj_pos_radius.data[_3150].xyz; + _3148 = true; + _3149 = true; + _3163_ladder_break = true; + break; + } + break; + } while(false); + if (_3163_ladder_break) + { + break; + } + _3150++; + continue; + } + if (_3148) + { + break; + } + _3148 = true; + _3149 = false; + break; + } while(false); + _2345 = _3149; + } + else + { + _2345 = false; + } + if (_2345) + { + _2343 = false; + _2346 = vec3(0.0); + _2344 = false; + _2345 = true; + break; + } + if (_5389 > 0.0) + { + _2350 = _5386 > 634500022272.0; + } + else + { + _2350 = false; + } + if (_2350) + { + _2343 = false; + _2346 = vec3(0.0); + _2344 = false; + _2345 = false; + break; + } + _2342++; + continue; + } + vec3 _2528 = normalize(vec3(_5383, _5384, _5385) - cam.u_cam_pos); + vec3 _3209 = dFdx(_2528); + vec3 _3212 = dFdy(_2528); + vec3 _2351; + if (_2343) + { + _2351 = _2346; + } + else + { + if (_2344) + { + _2351 = vec3(0.0); + } + else + { + if (_2345) + { + vec3 _2552 = vec3(_5383, _5384, _5385); + _2351 = _5453.xyz * (0.100000001490116119384765625 + (0.89999997615814208984375 * max(dot(normalize(_2552 - _5454), normalize(cam.u_cam_pos - _2552)), 0.0))); + } + else + { + _2351 = textureLod(u_HDRIEnvironment, _2528, clamp(log2(max(max(length(_3209), length(_3212)) * 666.66668701171875, 1.0)), 0.0, 10.0)).xyz; + } + } + } + entryPointParam_fragmentMain = vec4(_2351, 1.0); + break; + } + if (cam.u_moving != 0) + { + vec3 _3253 = normalize(((cam.u_cam_right * ((((2.0 * _v0.x) - 1.0) * cam.u_aspect) * cam.u_tan_half_fov)) - (cam.u_cam_up * ((1.0 - (2.0 * _v0.y)) * cam.u_tan_half_fov))) + cam.u_cam_forward); + float _3458 = length(cam.u_cam_pos); + float _3461 = acos(cam.u_cam_pos.z / _3458); + float _3463 = atan(cam.u_cam_pos.y, cam.u_cam_pos.x); + float _3464 = _3253.x; + float _3465 = _3253.y; + float _3466 = _3253.z; + float _3479 = (((sin(_3461) * cos(_3463)) * _3464) + ((sin(_3461) * sin(_3463)) * _3465)) + (cos(_3461) * _3466); + float _3493 = ((((cos(_3461) * cos(_3463)) * _3464) + ((cos(_3461) * sin(_3463)) * _3465)) - (sin(_3461) * _3466)) / _3458; + float _3503 = (((-sin(_3463)) * _3464) + (cos(_3463) * _3465)) / (_3458 * sin(_3461)); + float _3510 = 1.0 - (12689999872.0 / _3458); + float _5225 = cam.u_cam_pos.x; + float _5226 = cam.u_cam_pos.y; + float _5227 = cam.u_cam_pos.z; + float _5228 = _3458; + float _5229 = _3461; + float _5230 = _3463; + float _5231 = _3479; + float _5232 = _3493; + float _5233 = _3503; + float _5234 = _3510 * sqrt(((_3479 * _3479) / _3510) + ((_3458 * _3458) * ((_3493 * _3493) + (((sin(_3461) * sin(_3461)) * _3503) * _3503)))); + vec4 _5295 = vec4(0.0); + vec3 _5296 = vec3(0.0); + int _3215; + if (cam.u_moving != 0) + { + _3215 = sim.u_max_steps_moving; + } + else + { + _3215 = sim.u_max_steps_static; + } + if (_3215 <= 0) + { + if (cam.u_moving != 0) + { + _3215 = 12000; + } + else + { + _3215 = 8000; + } + } + float _3216; + if (sim.u_early_exit_distance > 0.0) + { + _3216 = sim.u_early_exit_distance; + } + else + { + _3216 = 1999999991808.0; + } + int _3217 = 0; + bool _3218; + bool _3219; + bool _3220; + vec3 _3221; + bool _3225; + bool _3531; + float _3532; + vec3 _3570; + vec3 _3680; + bool _3681; + float _3682; + vec3 _3784; + float _3785; + bool _4024; + for (;;) + { + if (!(_3217 < _3215)) + { + _3218 = false; + _3221 = vec3(0.0); + _3219 = false; + _3220 = false; + break; + } + if (_5228 <= 12689999872.0) + { + _3218 = false; + _3221 = vec3(0.0); + _3219 = true; + _3220 = false; + break; + } + if (_5228 > _3216) + { + _3218 = true; + } + else + { + _3218 = _5228 > 1000000015047466219876688855040.0; + } + if (_3218) + { + _3218 = false; + _3221 = vec3(0.0); + _3219 = false; + _3220 = false; + break; + } + float _3312 = _5225; + float _3314 = _5226; + float _3316 = _5227; + vec3 _3317 = vec3(_3312, _3314, _3316); + float _5301 = _3314; + float _3539 = 0.0199999995529651641845703125 * max(_5228 - 19035000832.0, 0.0); + if (length(vec2(_3312, _3316)) < (disk.u_outer_radius * 3.0)) + { + _3531 = abs(_5301) < (disk.u_thickness * 8.0); + } + else + { + _3531 = false; + } + if (_3531) + { + _3532 = min(_3539, disk.u_thickness); + } + else + { + _3532 = _3539; + } + float _3568 = clamp(_3532, 1000000.0, 20000000000.0); + float _5291 = _5233; + float _5290 = _5232; + float _5289 = _5231; + float _5288 = _5230; + float _5287 = _5229; + float _5286 = _5228; + float _3636 = 1.0 - (12689999872.0 / _5286); + float _3639 = _5234 / _3636; + vec3 _3640 = vec3(_5289, _5290, _5291); + float _3643 = (2.0 * _5286) * _5286; + float _3655 = sin(_5287); + vec3 _5664 = _3570; + _5664.x = ((((((-12689999872.0) / _3643) * _3636) * _3639) * _3639) + (((12689999872.0 / (_3643 * _3636)) * _5289) * _5289)) + (_5286 * ((_5290 * _5290) + (((_3655 * _3655) * _5291) * _5291))); + float _3663 = (-2.0) * _5289; + float _3666 = cos(_5287); + _5664.y = ((_3663 * _5290) / _5286) + (((_3655 * _3666) * _5291) * _5291); + _5664.z = ((_3663 * _5291) / _5286) - ((((2.0 * _3666) / _3655) * _5290) * _5291); + _3570 = _5664; + float _3580 = _5286 + (_3568 * _3640.x); + _5228 = _3580; + float _3586 = _5287 + (_3568 * _3640.y); + _5229 = _3586; + float _3592 = _5288 + (_3568 * _3640.z); + _5230 = _3592; + _5231 = _5289 + (_3568 * _5664.x); + _5232 = _5290 + (_3568 * _5664.y); + _5233 = _5291 + (_3568 * _5664.z); + float _3615 = (_3580 * sin(_3586)) * cos(_3592); + _5225 = _3615; + float _3620 = (_3580 * sin(_3586)) * sin(_3592); + _5226 = _3620; + float _3623 = _3580 * cos(_3586); + _5227 = _3623; + vec3 _3324 = vec3(_3615, _3620, _3623); + bool _3328 = (_3314 * _3620) < 0.0; + if (_3328) + { + _3219 = true; + } + else + { + _3219 = abs(_3620) <= disk.u_thickness; + } + if (_3219) + { + if (_3328) + { + _3221 = mix(_3317, _3324, vec3(_3314 / (_3314 - _3620))); + } + else + { + _3221 = _3324; + } + float _3348 = length(vec2(_3221.xz)); + if (_3348 >= max(disk.u_inner_radius, 38070001664.0)) + { + _3220 = _3348 <= disk.u_outer_radius; + } + else + { + _3220 = false; + } + if (_3220) + { + do + { + float _3689 = length(vec2(_3221.xz)); + float _3692 = max(disk.u_inner_radius, 38070001664.0); + if (_3689 < _3692) + { + _3681 = true; + } + else + { + _3681 = _3689 > disk.u_outer_radius; + } + if (_3681) + { + _3680 = vec3(0.0); + break; + } + float _3703 = _3689 / _3692; + float _3712 = pow(max((1.0 - sqrt(1.0 / _3703)) / ((_3703 * _3703) * _3703), 0.0) * 17.5746936798095703125, 0.25); + float _3734 = sqrt(max(1.0 - (19035000832.0 / _3689), 0.0)) / max(1.0 - dot(normalize(cross(vec3(0.0, 1.0, 0.0), normalize(vec3(_3221.x, 0.0, _3221.z)))) * sqrt(6344999936.0 / max(_3689 - 12689999872.0, 1.0)), -normalize(_3324 - _3317)), 0.001000000047497451305389404296875); + float _3789 = clamp(_3734 * (disk.u_temperature * _3712), 1000.0, 40000.0) * 0.00999999977648258209228515625; + bool _3791 = _3789 <= 66.0; + if (_3791) + { + _3785 = 1.0; + } + else + { + _3785 = clamp(1.29293620586395263671875 * pow(_3789 - 60.0, -0.133204758167266845703125), 0.0, 1.0); + } + vec3 _5670 = _3784; + _5670.x = _3785; + _3784 = _5670; + if (_3791) + { + _3785 = clamp((0.390081584453582763671875 * log(_3789)) - 0.6318414211273193359375, 0.0, 1.0); + } + else + { + _3785 = clamp(1.129890918731689453125 * pow(_3789 - 60.0, -0.075514845550060272216796875), 0.0, 1.0); + } + vec3 _5672 = _3784; + _5672.y = _3785; + _3784 = _5672; + if (_3789 >= 66.0) + { + _3785 = 1.0; + } + else + { + if (_3789 <= 19.0) + { + _3785 = 0.0; + } + else + { + _3785 = clamp((0.54320681095123291015625 * log(_3789 - 10.0)) - 1.19625413417816162109375, 0.0, 1.0); + } + } + vec3 _5674 = _3784; + _5674.z = _3785; + _3784 = _5674; + float _3746 = (pow(_3712, 2.0) * pow(_3734, 4.0)) * (smoothstep(_3692, _3692 * 1.12000000476837158203125, _3689) * (1.0 - smoothstep(disk.u_outer_radius * 0.87999999523162841796875, disk.u_outer_radius, _3689))); + if (disk.u_turbulence > 0.0) + { + float _3756 = (sim.u_time * 0.300000011920928955078125) / sqrt(_3703); + float _3757 = cos(_3756); + float _3759 = sin(_3756); + vec3 _3829 = (vec3((_3221.x * _3757) - (_3221.z * _3759), 0.0, (_3221.x * _3759) + (_3221.z * _3757)) * 1.0000000133514319600180897396058e-10) * 3.0; + int _3830 = 0; + float _3831 = 0.5; + float _3832 = 0.0; + for (;;) + { + if (!(_3830 < 3)) + { + break; + } + vec3 _3863 = floor(_3829); + vec3 _3864 = fract(_3829); + vec3 _3869 = (_3864 * _3864) * (vec3(3.0) - (_3864 * 2.0)); + vec3 _3898 = fract(_3863 * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3904 = _3898 + vec3(dot(_3898, _3898.yxz + vec3(33.3300018310546875))); + vec3 _3914 = fract((_3863 + vec3(1.0, 0.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3920 = _3914 + vec3(dot(_3914, _3914.yxz + vec3(33.3300018310546875))); + vec3 _3930 = fract((_3863 + vec3(0.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3936 = _3930 + vec3(dot(_3930, _3930.yxz + vec3(33.3300018310546875))); + vec3 _3946 = fract((_3863 + vec3(1.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3952 = _3946 + vec3(dot(_3946, _3946.yxz + vec3(33.3300018310546875))); + vec3 _3962 = fract((_3863 + vec3(0.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3968 = _3962 + vec3(dot(_3962, _3962.yxz + vec3(33.3300018310546875))); + vec3 _3978 = fract((_3863 + vec3(1.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _3984 = _3978 + vec3(dot(_3978, _3978.yxz + vec3(33.3300018310546875))); + vec3 _3994 = fract((_3863 + vec3(0.0, 1.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4000 = _3994 + vec3(dot(_3994, _3994.yxz + vec3(33.3300018310546875))); + vec3 _4010 = fract((_3863 + vec3(1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4016 = _4010 + vec3(dot(_4010, _4010.yxz + vec3(33.3300018310546875))); + float _3885 = _3869.x; + float _3888 = _3869.y; + float _3845 = _3831; + float _3846 = _3845 * mix(mix(mix(fract((_3904.x + _3904.y) * _3904.z), fract((_3920.x + _3920.y) * _3920.z), _3885), mix(fract((_3936.x + _3936.y) * _3936.z), fract((_3952.x + _3952.y) * _3952.z), _3885), _3888), mix(mix(fract((_3968.x + _3968.y) * _3968.z), fract((_3984.x + _3984.y) * _3984.z), _3885), mix(fract((_4000.x + _4000.y) * _4000.z), fract((_4016.x + _4016.y) * _4016.z), _3885), _3888), _3869.z); + _3829 = (_3829 * 2.0) + vec3(100.0, 200.0, 300.0); + _3830++; + _3831 = _3845 * 0.5; + _3832 += _3846; + continue; + } + _3682 = _3746 * max(1.0 + (disk.u_turbulence * (_3832 - 0.5)), 0.0); + } + else + { + _3682 = _3746; + } + _3680 = (_5674 * _3682) * max(disk.u_brightness, 0.0); + break; + } while(false); + _3218 = true; + _3221 = _3680; + _3219 = false; + _3220 = false; + break; + } + } + if ((_3217 - 5 * (_3217 / 5)) == 0) + { + float _5308 = _5225; + float _5309 = _5226; + float _5310 = _5227; + _4023 = false; + do + { + vec3 _4035 = vec3(_5308, _5309, _5310); + int _4025 = 0; + for (;;) + { + bool _4038_ladder_break = false; + do + { + if (!(_4025 < obj.u_num_objects)) + { + _4038_ladder_break = true; + break; + } + vec3 _4054 = _4035 - obj.u_obj_pos_radius.data[_4025].xyz; + float _4055 = dot(_4054, _4054); + float _4056 = obj.u_obj_pos_radius.data[_4025].w * obj.u_obj_pos_radius.data[_4025].w; + if (_4055 > (_4056 * 4.0)) + { + break; + } + if (_4055 <= _4056) + { + _5295 = obj.u_obj_color.data[_4025]; + _5296 = obj.u_obj_pos_radius.data[_4025].xyz; + _4023 = true; + _4024 = true; + _4038_ladder_break = true; + break; + } + break; + } while(false); + if (_4038_ladder_break) + { + break; + } + _4025++; + continue; + } + if (_4023) + { + break; + } + _4023 = true; + _4024 = false; + break; + } while(false); + _3220 = _4024; + } + else + { + _3220 = false; + } + if (_3220) + { + _3218 = false; + _3221 = vec3(0.0); + _3219 = false; + _3220 = true; + break; + } + if (_5231 > 0.0) + { + _3225 = _5228 > 634500022272.0; + } + else + { + _3225 = false; + } + if (_3225) + { + _3218 = false; + _3221 = vec3(0.0); + _3219 = false; + _3220 = false; + break; + } + _3217++; + continue; + } + vec3 _3403 = normalize(vec3(_5225, _5226, _5227) - cam.u_cam_pos); + vec3 _4084 = dFdx(_3403); + vec3 _4087 = dFdy(_3403); + vec3 _3226; + if (_3218) + { + _3226 = _3221; + } + else + { + if (_3219) + { + _3226 = vec3(0.0); + } + else + { + if (_3220) + { + vec3 _3427 = vec3(_5225, _5226, _5227); + _3226 = _5295.xyz * (0.100000001490116119384765625 + (0.89999997615814208984375 * max(dot(normalize(_3427 - _5296), normalize(cam.u_cam_pos - _3427)), 0.0))); + } + else + { + _3226 = textureLod(u_HDRIEnvironment, _3403, clamp(log2(max(max(length(_4084), length(_4087)) * 666.66668701171875, 1.0)), 0.0, 10.0)).xyz; + } + } + } + entryPointParam_fragmentMain = vec4(clamp((_3226 * ((_3226 * 2.5099999904632568359375) + vec3(0.02999999932944774627685546875))) / ((_3226 * ((_3226 * 2.4300000667572021484375) + vec3(0.589999973773956298828125))) + vec3(0.14000000059604644775390625)), vec3(0.0), vec3(1.0)), 1.0); + break; + } + float _4104 = dFdx(_v0.x); + float _4107 = dFdy(_v0.y); + vec2 _1390 = vec2(_4104, _4107); + int i = 0; + vec3 sum = vec3(0.0); + int _4110; + float _4111; + bool _4113; + bool _4114; + bool _4115; + vec3 _4116; + bool _4120; + vec3 _4121; + bool _4426; + float _4427; + vec3 _4465; + vec3 _4575; + bool _4576; + float _4577; + vec3 _4679; + float _4680; + bool _4919; + for (;;) + { + if (!(i < 4)) + { + break; + } + vec2 _1413 = _v0 + (_1399[i] * _1390); + vec3 _4148 = normalize(((cam.u_cam_right * ((((2.0 * _1413.x) - 1.0) * cam.u_aspect) * cam.u_tan_half_fov)) - (cam.u_cam_up * ((1.0 - (2.0 * _1413.y)) * cam.u_tan_half_fov))) + cam.u_cam_forward); + float _4353 = length(cam.u_cam_pos); + float _4356 = acos(cam.u_cam_pos.z / _4353); + float _4358 = atan(cam.u_cam_pos.y, cam.u_cam_pos.x); + float _4359 = _4148.x; + float _4360 = _4148.y; + float _4361 = _4148.z; + float _4374 = (((sin(_4356) * cos(_4358)) * _4359) + ((sin(_4356) * sin(_4358)) * _4360)) + (cos(_4356) * _4361); + float _4388 = ((((cos(_4356) * cos(_4358)) * _4359) + ((cos(_4356) * sin(_4358)) * _4360)) - (sin(_4356) * _4361)) / _4353; + float _4398 = (((-sin(_4358)) * _4359) + (cos(_4358) * _4360)) / (_4353 * sin(_4356)); + float _4405 = 1.0 - (12689999872.0 / _4353); + float _5067 = cam.u_cam_pos.x; + float _5068 = cam.u_cam_pos.y; + float _5069 = cam.u_cam_pos.z; + float _5070 = _4353; + float _5071 = _4356; + float _5072 = _4358; + float _5073 = _4374; + float _5074 = _4388; + float _5075 = _4398; + float _5076 = _4405 * sqrt(((_4374 * _4374) / _4405) + ((_4353 * _4353) * ((_4388 * _4388) + (((sin(_4356) * sin(_4356)) * _4398) * _4398)))); + vec4 _5137 = vec4(0.0); + vec3 _5138 = vec3(0.0); + if (cam.u_moving != 0) + { + _4110 = sim.u_max_steps_moving; + } + else + { + _4110 = sim.u_max_steps_static; + } + if (_4110 <= 0) + { + if (cam.u_moving != 0) + { + _4110 = 12000; + } + else + { + _4110 = 8000; + } + } + if (sim.u_early_exit_distance > 0.0) + { + _4111 = sim.u_early_exit_distance; + } + else + { + _4111 = 1999999991808.0; + } + int _4112 = 0; + for (;;) + { + if (!(_4112 < _4110)) + { + _4113 = false; + _4116 = vec3(0.0); + _4114 = false; + _4115 = false; + break; + } + if (_5070 <= 12689999872.0) + { + _4113 = false; + _4116 = vec3(0.0); + _4114 = true; + _4115 = false; + break; + } + if (_5070 > _4111) + { + _4113 = true; + } + else + { + _4113 = _5070 > 1000000015047466219876688855040.0; + } + if (_4113) + { + _4113 = false; + _4116 = vec3(0.0); + _4114 = false; + _4115 = false; + break; + } + float _4207 = _5067; + float _4209 = _5068; + float _4211 = _5069; + vec3 _4212 = vec3(_4207, _4209, _4211); + float _5143 = _4209; + float _4434 = 0.0199999995529651641845703125 * max(_5070 - 19035000832.0, 0.0); + if (length(vec2(_4207, _4211)) < (disk.u_outer_radius * 3.0)) + { + _4426 = abs(_5143) < (disk.u_thickness * 8.0); + } + else + { + _4426 = false; + } + if (_4426) + { + _4427 = min(_4434, disk.u_thickness); + } + else + { + _4427 = _4434; + } + float _4463 = clamp(_4427, 1000000.0, 20000000000.0); + float _5133 = _5075; + float _5132 = _5074; + float _5131 = _5073; + float _5130 = _5072; + float _5129 = _5071; + float _5128 = _5070; + float _4531 = 1.0 - (12689999872.0 / _5128); + float _4534 = _5076 / _4531; + vec3 _4535 = vec3(_5131, _5132, _5133); + float _4538 = (2.0 * _5128) * _5128; + float _4550 = sin(_5129); + vec3 _5676 = _4465; + _5676.x = ((((((-12689999872.0) / _4538) * _4531) * _4534) * _4534) + (((12689999872.0 / (_4538 * _4531)) * _5131) * _5131)) + (_5128 * ((_5132 * _5132) + (((_4550 * _4550) * _5133) * _5133))); + float _4558 = (-2.0) * _5131; + float _4561 = cos(_5129); + _5676.y = ((_4558 * _5132) / _5128) + (((_4550 * _4561) * _5133) * _5133); + _5676.z = ((_4558 * _5133) / _5128) - ((((2.0 * _4561) / _4550) * _5132) * _5133); + _4465 = _5676; + float _4475 = _5128 + (_4463 * _4535.x); + _5070 = _4475; + float _4481 = _5129 + (_4463 * _4535.y); + _5071 = _4481; + float _4487 = _5130 + (_4463 * _4535.z); + _5072 = _4487; + _5073 = _5131 + (_4463 * _5676.x); + _5074 = _5132 + (_4463 * _5676.y); + _5075 = _5133 + (_4463 * _5676.z); + float _4510 = (_4475 * sin(_4481)) * cos(_4487); + _5067 = _4510; + float _4515 = (_4475 * sin(_4481)) * sin(_4487); + _5068 = _4515; + float _4518 = _4475 * cos(_4481); + _5069 = _4518; + vec3 _4219 = vec3(_4510, _4515, _4518); + bool _4223 = (_4209 * _4515) < 0.0; + if (_4223) + { + _4114 = true; + } + else + { + _4114 = abs(_4515) <= disk.u_thickness; + } + if (_4114) + { + if (_4223) + { + _4116 = mix(_4212, _4219, vec3(_4209 / (_4209 - _4515))); + } + else + { + _4116 = _4219; + } + float _4243 = length(vec2(_4116.xz)); + if (_4243 >= max(disk.u_inner_radius, 38070001664.0)) + { + _4115 = _4243 <= disk.u_outer_radius; + } + else + { + _4115 = false; + } + if (_4115) + { + do + { + float _4584 = length(vec2(_4116.xz)); + float _4587 = max(disk.u_inner_radius, 38070001664.0); + if (_4584 < _4587) + { + _4576 = true; + } + else + { + _4576 = _4584 > disk.u_outer_radius; + } + if (_4576) + { + _4575 = vec3(0.0); + break; + } + float _4598 = _4584 / _4587; + float _4607 = pow(max((1.0 - sqrt(1.0 / _4598)) / ((_4598 * _4598) * _4598), 0.0) * 17.5746936798095703125, 0.25); + float _4629 = sqrt(max(1.0 - (19035000832.0 / _4584), 0.0)) / max(1.0 - dot(normalize(cross(vec3(0.0, 1.0, 0.0), normalize(vec3(_4116.x, 0.0, _4116.z)))) * sqrt(6344999936.0 / max(_4584 - 12689999872.0, 1.0)), -normalize(_4219 - _4212)), 0.001000000047497451305389404296875); + float _4684 = clamp(_4629 * (disk.u_temperature * _4607), 1000.0, 40000.0) * 0.00999999977648258209228515625; + bool _4686 = _4684 <= 66.0; + if (_4686) + { + _4680 = 1.0; + } + else + { + _4680 = clamp(1.29293620586395263671875 * pow(_4684 - 60.0, -0.133204758167266845703125), 0.0, 1.0); + } + vec3 _5682 = _4679; + _5682.x = _4680; + _4679 = _5682; + if (_4686) + { + _4680 = clamp((0.390081584453582763671875 * log(_4684)) - 0.6318414211273193359375, 0.0, 1.0); + } + else + { + _4680 = clamp(1.129890918731689453125 * pow(_4684 - 60.0, -0.075514845550060272216796875), 0.0, 1.0); + } + vec3 _5684 = _4679; + _5684.y = _4680; + _4679 = _5684; + if (_4684 >= 66.0) + { + _4680 = 1.0; + } + else + { + if (_4684 <= 19.0) + { + _4680 = 0.0; + } + else + { + _4680 = clamp((0.54320681095123291015625 * log(_4684 - 10.0)) - 1.19625413417816162109375, 0.0, 1.0); + } + } + vec3 _5686 = _4679; + _5686.z = _4680; + _4679 = _5686; + float _4641 = (pow(_4607, 2.0) * pow(_4629, 4.0)) * (smoothstep(_4587, _4587 * 1.12000000476837158203125, _4584) * (1.0 - smoothstep(disk.u_outer_radius * 0.87999999523162841796875, disk.u_outer_radius, _4584))); + if (disk.u_turbulence > 0.0) + { + float _4651 = (sim.u_time * 0.300000011920928955078125) / sqrt(_4598); + float _4652 = cos(_4651); + float _4654 = sin(_4651); + vec3 _4724 = (vec3((_4116.x * _4652) - (_4116.z * _4654), 0.0, (_4116.x * _4654) + (_4116.z * _4652)) * 1.0000000133514319600180897396058e-10) * 3.0; + int _4725 = 0; + float _4726 = 0.5; + float _4727 = 0.0; + for (;;) + { + if (!(_4725 < 3)) + { + break; + } + vec3 _4758 = floor(_4724); + vec3 _4759 = fract(_4724); + vec3 _4764 = (_4759 * _4759) * (vec3(3.0) - (_4759 * 2.0)); + vec3 _4793 = fract(_4758 * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4799 = _4793 + vec3(dot(_4793, _4793.yxz + vec3(33.3300018310546875))); + vec3 _4809 = fract((_4758 + vec3(1.0, 0.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4815 = _4809 + vec3(dot(_4809, _4809.yxz + vec3(33.3300018310546875))); + vec3 _4825 = fract((_4758 + vec3(0.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4831 = _4825 + vec3(dot(_4825, _4825.yxz + vec3(33.3300018310546875))); + vec3 _4841 = fract((_4758 + vec3(1.0, 1.0, 0.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4847 = _4841 + vec3(dot(_4841, _4841.yxz + vec3(33.3300018310546875))); + vec3 _4857 = fract((_4758 + vec3(0.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4863 = _4857 + vec3(dot(_4857, _4857.yxz + vec3(33.3300018310546875))); + vec3 _4873 = fract((_4758 + vec3(1.0, 0.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4879 = _4873 + vec3(dot(_4873, _4873.yxz + vec3(33.3300018310546875))); + vec3 _4889 = fract((_4758 + vec3(0.0, 1.0, 1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4895 = _4889 + vec3(dot(_4889, _4889.yxz + vec3(33.3300018310546875))); + vec3 _4905 = fract((_4758 + vec3(1.0)) * vec3(0.103100001811981201171875, 0.10300000011920928955078125, 0.097300000488758087158203125)); + vec3 _4911 = _4905 + vec3(dot(_4905, _4905.yxz + vec3(33.3300018310546875))); + float _4780 = _4764.x; + float _4783 = _4764.y; + float _4740 = _4726; + float _4741 = _4740 * mix(mix(mix(fract((_4799.x + _4799.y) * _4799.z), fract((_4815.x + _4815.y) * _4815.z), _4780), mix(fract((_4831.x + _4831.y) * _4831.z), fract((_4847.x + _4847.y) * _4847.z), _4780), _4783), mix(mix(fract((_4863.x + _4863.y) * _4863.z), fract((_4879.x + _4879.y) * _4879.z), _4780), mix(fract((_4895.x + _4895.y) * _4895.z), fract((_4911.x + _4911.y) * _4911.z), _4780), _4783), _4764.z); + _4724 = (_4724 * 2.0) + vec3(100.0, 200.0, 300.0); + _4725++; + _4726 = _4740 * 0.5; + _4727 += _4741; + continue; + } + _4577 = _4641 * max(1.0 + (disk.u_turbulence * (_4727 - 0.5)), 0.0); + } + else + { + _4577 = _4641; + } + _4575 = (_5686 * _4577) * max(disk.u_brightness, 0.0); + break; + } while(false); + _4113 = true; + _4116 = _4575; + _4114 = false; + _4115 = false; + break; + } + } + if ((_4112 - 5 * (_4112 / 5)) == 0) + { + float _5150 = _5067; + float _5151 = _5068; + float _5152 = _5069; + _4918 = false; + do + { + vec3 _4930 = vec3(_5150, _5151, _5152); + int _4920 = 0; + for (;;) + { + bool _4933_ladder_break = false; + do + { + if (!(_4920 < obj.u_num_objects)) + { + _4933_ladder_break = true; + break; + } + vec3 _4949 = _4930 - obj.u_obj_pos_radius.data[_4920].xyz; + float _4950 = dot(_4949, _4949); + float _4951 = obj.u_obj_pos_radius.data[_4920].w * obj.u_obj_pos_radius.data[_4920].w; + if (_4950 > (_4951 * 4.0)) + { + break; + } + if (_4950 <= _4951) + { + _5137 = obj.u_obj_color.data[_4920]; + _5138 = obj.u_obj_pos_radius.data[_4920].xyz; + _4918 = true; + _4919 = true; + _4933_ladder_break = true; + break; + } + break; + } while(false); + if (_4933_ladder_break) + { + break; + } + _4920++; + continue; + } + if (_4918) + { + break; + } + _4918 = true; + _4919 = false; + break; + } while(false); + _4115 = _4919; + } + else + { + _4115 = false; + } + if (_4115) + { + _4113 = false; + _4116 = vec3(0.0); + _4114 = false; + _4115 = true; + break; + } + if (_5073 > 0.0) + { + _4120 = _5070 > 634500022272.0; + } + else + { + _4120 = false; + } + if (_4120) + { + _4113 = false; + _4116 = vec3(0.0); + _4114 = false; + _4115 = false; + break; + } + _4112++; + continue; + } + vec3 _4298 = normalize(vec3(_5067, _5068, _5069) - cam.u_cam_pos); + vec3 _4979 = dFdx(_4298); + vec3 _4982 = dFdy(_4298); + if (_4113) + { + _4121 = _4116; + } + else + { + if (_4114) + { + _4121 = vec3(0.0); + } + else + { + if (_4115) + { + vec3 _4322 = vec3(_5067, _5068, _5069); + _4121 = _5137.xyz * (0.100000001490116119384765625 + (0.89999997615814208984375 * max(dot(normalize(_4322 - _5138), normalize(cam.u_cam_pos - _4322)), 0.0))); + } + else + { + _4121 = textureLod(u_HDRIEnvironment, _4298, clamp(log2(max(max(length(_4979), length(_4982)) * 666.66668701171875, 1.0)), 0.0, 10.0)).xyz; + } + } + } + i++; + sum += _4121; + continue; + } + vec3 _1426 = sum * 0.25; + entryPointParam_fragmentMain = vec4(clamp((_1426 * ((_1426 * 2.5099999904632568359375) + vec3(0.02999999932944774627685546875))) / ((_1426 * ((_1426 * 2.4300000667572021484375) + vec3(0.589999973773956298828125))) + vec3(0.14000000059604644775390625)), vec3(0.0), vec3(1.0)), 1.0); + break; + } while(false); +} + + diff --git a/assets/shaders/generated/geodesic.vertexMain.spv b/assets/shaders/generated/geodesic.vertexMain.spv Binary files differnew file mode 100644 index 0000000..95ef54a --- /dev/null +++ b/assets/shaders/generated/geodesic.vertexMain.spv diff --git a/assets/shaders/generated/grid.fragmentMain.spv b/assets/shaders/generated/grid.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..9ae1459 --- /dev/null +++ b/assets/shaders/generated/grid.fragmentMain.spv diff --git a/assets/shaders/generated/grid.glsl b/assets/shaders/generated/grid.glsl new file mode 100644 index 0000000..b69779e --- /dev/null +++ b/assets/shaders/generated/grid.glsl @@ -0,0 +1,45 @@ +#type vertex +#version 410 + +layout(std140) uniform Grid +{ + layout(row_major) mat4 u_view_projection; + layout(row_major) mat4 u_transform; + float u_grid_size; + vec3 u_grid_color; + float u_grid_alpha; + vec3 u_camera_pos; +} gu; + +layout(location = 0) in vec3 input_position; + +mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; } + +void main() +{ + gl_Position = (vec4(input_position * (gu.u_grid_size * 0.0199999995529651641845703125), 1.0) * spvWorkaroundRowMajor(gu.u_transform)) * spvWorkaroundRowMajor(gu.u_view_projection); +} + + +#type fragment +#version 410 + +layout(std140) uniform Grid +{ + layout(row_major) mat4 u_view_projection; + layout(row_major) mat4 u_transform; + float u_grid_size; + vec3 u_grid_color; + float u_grid_alpha; + vec3 u_camera_pos; +} gu; + +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + float _17 = length(gu.u_camera_pos); + entryPointParam_fragmentMain = vec4(gu.u_grid_color, (gu.u_grid_alpha * (1.0 - clamp(_17 * 0.00999999977648258209228515625, 0.0, 0.800000011920928955078125))) * (1.0 - clamp(_17 * 0.0199999995529651641845703125, 0.0, 0.89999997615814208984375))); +} + + diff --git a/assets/shaders/generated/grid.vertexMain.spv b/assets/shaders/generated/grid.vertexMain.spv Binary files differnew file mode 100644 index 0000000..121747d --- /dev/null +++ b/assets/shaders/generated/grid.vertexMain.spv diff --git a/assets/shaders/generated/skybox.fragmentMain.spv b/assets/shaders/generated/skybox.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..dab5881 --- /dev/null +++ b/assets/shaders/generated/skybox.fragmentMain.spv diff --git a/assets/shaders/generated/skybox.glsl b/assets/shaders/generated/skybox.glsl new file mode 100644 index 0000000..aa319a2 --- /dev/null +++ b/assets/shaders/generated/skybox.glsl @@ -0,0 +1,35 @@ +#type vertex +#version 410 + +layout(std140) uniform Skybox +{ + layout(row_major) mat4 u_projection; + layout(row_major) mat4 u_view; +} sb; + +layout(location = 0) in vec3 input_position; +layout(location = 0) out vec3 _v0; + +mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; } + +void main() +{ + gl_Position = ((vec4(input_position, 1.0) * spvWorkaroundRowMajor(sb.u_view)) * spvWorkaroundRowMajor(sb.u_projection)).xyww; + _v0 = input_position; +} + + +#type fragment +#version 410 + +uniform samplerCube u_Skybox; + +layout(location = 0) in vec3 _v0; +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + entryPointParam_fragmentMain = texture(u_Skybox, _v0); +} + + diff --git a/assets/shaders/generated/skybox.vertexMain.spv b/assets/shaders/generated/skybox.vertexMain.spv Binary files differnew file mode 100644 index 0000000..404ed76 --- /dev/null +++ b/assets/shaders/generated/skybox.vertexMain.spv diff --git a/assets/shaders/generated/sphere.fragmentMain.spv b/assets/shaders/generated/sphere.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..f63de6a --- /dev/null +++ b/assets/shaders/generated/sphere.fragmentMain.spv diff --git a/assets/shaders/generated/sphere.glsl b/assets/shaders/generated/sphere.glsl new file mode 100644 index 0000000..8580c9e --- /dev/null +++ b/assets/shaders/generated/sphere.glsl @@ -0,0 +1,76 @@ +#type vertex +#version 410 + +layout(std140) uniform Sphere +{ + layout(row_major) mat4 u_view_projection; + layout(row_major) mat4 u_transform; + vec3 u_color; + float u_specular; + float u_emission; + vec3 u_light_pos; + vec3 u_camera_pos; + int u_is_selected; + vec3 u_outline_color; + float u_outline_width; +} su; + +layout(location = 0) in vec3 input_position; +layout(location = 1) in vec3 input_normal; +layout(location = 0) out vec3 _v0; +layout(location = 1) out vec3 _v1; + +mat4 spvWorkaroundRowMajor(mat4 wrap) { return wrap; } + +void main() +{ + vec3 _29 = (vec4(input_position, 1.0) * spvWorkaroundRowMajor(su.u_transform)).xyz; + gl_Position = vec4(_29, 1.0) * spvWorkaroundRowMajor(su.u_view_projection); + _v0 = input_normal * mat3(spvWorkaroundRowMajor(su.u_transform)[0].xyz, spvWorkaroundRowMajor(su.u_transform)[1].xyz, spvWorkaroundRowMajor(su.u_transform)[2].xyz); + _v1 = _29; +} + + +#type fragment +#version 410 + +layout(std140) uniform Sphere +{ + layout(row_major) mat4 u_view_projection; + layout(row_major) mat4 u_transform; + vec3 u_color; + float u_specular; + float u_emission; + vec3 u_light_pos; + vec3 u_camera_pos; + int u_is_selected; + vec3 u_outline_color; + float u_outline_width; +} su; + +uniform samplerCube u_HDRIEnvironment; + +layout(location = 0) in vec3 _v0; +layout(location = 1) in vec3 _v1; +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + vec3 _15 = normalize(_v0); + vec3 _30 = normalize(su.u_light_pos - _v1); + vec3 _36 = normalize(su.u_camera_pos - _v1); + vec4 _sampled = texture(u_HDRIEnvironment, _15); + vec3 result_1 = ((su.u_color * (vec3(max(dot(_15, _30), 0.0)) + (_sampled.xyz * 0.300000011920928955078125))) + (vec3(su.u_specular, su.u_specular, su.u_specular) * pow(max(dot(_36, reflect(-_30, _15)), 0.0), 32.0))) + (su.u_color * su.u_emission); + vec3 result; + if (su.u_is_selected > 0) + { + result = mix(result_1, su.u_outline_color, vec3(step(1.0 - su.u_outline_width, 1.0 - max(dot(_15, _36), 0.0)))); + } + else + { + result = result_1; + } + entryPointParam_fragmentMain = vec4(result, 1.0); +} + + diff --git a/assets/shaders/generated/sphere.vertexMain.spv b/assets/shaders/generated/sphere.vertexMain.spv Binary files differnew file mode 100644 index 0000000..22f1582 --- /dev/null +++ b/assets/shaders/generated/sphere.vertexMain.spv diff --git a/assets/shaders/generated/textured_quad.fragmentMain.spv b/assets/shaders/generated/textured_quad.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..d884e27 --- /dev/null +++ b/assets/shaders/generated/textured_quad.fragmentMain.spv diff --git a/assets/shaders/generated/textured_quad.glsl b/assets/shaders/generated/textured_quad.glsl new file mode 100644 index 0000000..a342b86 --- /dev/null +++ b/assets/shaders/generated/textured_quad.glsl @@ -0,0 +1,28 @@ +#type vertex +#version 410 + +layout(location = 0) in vec2 input_position; +layout(location = 1) in vec2 input_texCoord; +layout(location = 0) out vec2 _v0; + +void main() +{ + gl_Position = vec4(input_position, 0.0, 1.0); + _v0 = input_texCoord; +} + + +#type fragment +#version 410 + +uniform sampler2D u_ScreenTexture; + +layout(location = 0) in vec2 _v0; +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + entryPointParam_fragmentMain = texture(u_ScreenTexture, _v0); +} + + diff --git a/assets/shaders/generated/textured_quad.vertexMain.spv b/assets/shaders/generated/textured_quad.vertexMain.spv Binary files differnew file mode 100644 index 0000000..95ef54a --- /dev/null +++ b/assets/shaders/generated/textured_quad.vertexMain.spv diff --git a/assets/shaders/generated/vk_pipeline_test.fragmentMain.spv b/assets/shaders/generated/vk_pipeline_test.fragmentMain.spv Binary files differnew file mode 100644 index 0000000..981e4b5 --- /dev/null +++ b/assets/shaders/generated/vk_pipeline_test.fragmentMain.spv diff --git a/assets/shaders/generated/vk_pipeline_test.glsl b/assets/shaders/generated/vk_pipeline_test.glsl new file mode 100644 index 0000000..91a8dee --- /dev/null +++ b/assets/shaders/generated/vk_pipeline_test.glsl @@ -0,0 +1,33 @@ +#type vertex +#version 410 +#ifdef GL_ARB_shader_draw_parameters +#extension GL_ARB_shader_draw_parameters : enable +#endif + +#ifdef GL_ARB_shader_draw_parameters +#define SPIRV_Cross_BaseVertex gl_BaseVertexARB +#else +uniform int SPIRV_Cross_BaseVertex; +#endif +layout(location = 0) out vec3 _v0; + +void main() +{ + vec2 p = vec2(float((uint(gl_VertexID - SPIRV_Cross_BaseVertex) << uint(1)) & 2u), float(uint(gl_VertexID - SPIRV_Cross_BaseVertex) & 2u)); + gl_Position = vec4((p * 2.0) - vec2(1.0), 0.0, 1.0); + _v0 = vec3(p, 0.5); +} + + +#type fragment +#version 410 + +layout(location = 0) in vec3 _v0; +layout(location = 0) out vec4 entryPointParam_fragmentMain; + +void main() +{ + entryPointParam_fragmentMain = vec4(_v0, 1.0); +} + + diff --git a/assets/shaders/generated/vk_pipeline_test.vertexMain.spv b/assets/shaders/generated/vk_pipeline_test.vertexMain.spv Binary files differnew file mode 100644 index 0000000..05fb85e --- /dev/null +++ b/assets/shaders/generated/vk_pipeline_test.vertexMain.spv diff --git a/docs/architecture.md b/docs/architecture.md index 98f2b6f..1e6560b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -313,16 +313,27 @@ a few points worth knowing: ## the build system -donut uses premake5 to generate gnu makefiles. both backends compile into one -binary; the choice between them is made at runtime from the saved settings, so -there is no separate "opengl build" and "vulkan build". - -generate and build (arm64 macos): +donut uses premake5 to generate the build: gnu makefiles on macos +(`premake5 gmake`) and a visual studio 2022 solution on windows +(`premake5 vs2022`). both backends compile into one binary; the choice between +them is made at runtime from the saved settings, so there is no separate "opengl +build" and "vulkan build". the one exception is windows without the vulkan sdk: +premake then leaves the vulkan backend out (`DONUT_NO_VULKAN`) and the binary is +opengl only. on windows `vulkan-1.dll` is delay-loaded, so a vulkan build still +starts on a machine with no vulkan driver, and any vulkan failure at startup falls +back to opengl. step-by-step instructions for both platforms are in the +[readme](../README.md#building). ```bash -premake5 gmake && make config=debug +premake5 gmake && make config=debug donut # macos +premake5 vs2022 # windows, then build donut.sln ``` +the shaders are authored once in slang (`assets/shaders/*.slang`) and compiled by +`tools/compile-shaders.sh` into `assets/shaders/generated/`: glsl 4.10 for opengl +and spir-v for vulkan. the generated files are committed, so windows builds use +them as-is; on macos the script runs as a prebuild step and keeps them current. + `premake5 clean` is wired up as a custom action that removes the generated build output (`bin/`, `bin-int/`, the makefiles) along with the transient runtime files (`logs/`, `config/`, `imgui.ini`), for a genuine from-scratch reset. diff --git a/premake5.lua b/premake5.lua index 27a11e2..88cf22a 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,5 +1,12 @@ output_dir = "%{cfg.buildcfg}-%{cfg.system}" +-- the LunarG installer sets this on windows. without it the vulkan backend is +-- left out of the build (DONUT_NO_VULKAN) and the app runs opengl only. +vulkan_sdk = os.getenv("VULKAN_SDK") +if os.target() == "windows" and not vulkan_sdk then + print("VULKAN_SDK is not set: building without the Vulkan backend (OpenGL only)") +end + include_dir = { glm = "ext/glm", glfw = "ext/glfw/include", @@ -38,19 +45,21 @@ function setup_c_target() runtime "Debug" symbols "On" - filter { "system:macosx or system:linux" } - buildoptions { - "-g", - "-fno-omit-frame-pointer", - "-fsanitize=address,undefined", - } + -- premake filters don't nest: this one has to name debug again, or the + -- sanitizers end up in every config + filter { "configurations:debug", "system:macosx or system:linux" } + buildoptions { + "-g", + "-fno-omit-frame-pointer", + "-fsanitize=address,undefined", + } - linkoptions { - "-fsanitize=address,undefined", - } - filter {} + linkoptions { + "-fsanitize=address,undefined", + } + filter {} - filter { "configurations:release", "configurations:dist" } + filter "configurations:release or dist" runtime "Release" optimize "Speed" filter {} @@ -78,19 +87,21 @@ function setup_cpp_target() runtime "Debug" symbols "On" - filter { "system:macosx or system:linux" } - buildoptions { - "-g", - "-fno-omit-frame-pointer", - "-fsanitize=address,undefined", - } + -- premake filters don't nest: this one has to name debug again, or the + -- sanitizers end up in every config + filter { "configurations:debug", "system:macosx or system:linux" } + buildoptions { + "-g", + "-fno-omit-frame-pointer", + "-fsanitize=address,undefined", + } - linkoptions { - "-fsanitize=address,undefined", - } - filter {} + linkoptions { + "-fsanitize=address,undefined", + } + filter {} - filter { "configurations:release", "configurations:dist" } + filter "configurations:release or dist" runtime "Release" optimize "Speed" filter {} @@ -335,6 +346,10 @@ project "imgui" externalincludedirs "/opt/homebrew/include" filter "system:windows" systemversion "latest" + if vulkan_sdk then + files "ext/imgui/backends/imgui_impl_vulkan.cpp" + externalincludedirs (vulkan_sdk .. "/Include") + end filter {} project "imguizmo" @@ -387,6 +402,7 @@ project "donut" } includedirs "src" + debugdir "%{wks.location}" -- every asset path is relative to the repo root externalincludedirs { "%{include_dir.glm}", @@ -413,7 +429,22 @@ project "donut" filter "system:windows" systemversion "latest" - links "opengl32.lib" + defines "NOMINMAX" -- glad drags in windows.h, whose min/max macros eat std::min/std::max + usestandardpreprocessor "On" -- msvc only knows __VA_OPT__ (log.h) with its conforming preprocessor + conformancemode "On" + buildoptions "/utf-8" + links "opengl32" + if vulkan_sdk then + externalincludedirs (vulkan_sdk .. "/Include") + libdirs (vulkan_sdk .. "/Lib") + -- delay-loaded, so a box without a Vulkan driver still starts (on OpenGL) + -- instead of the loader refusing to run the exe at all + links { "vulkan-1", "delayimp" } + linkoptions "/DELAYLOAD:vulkan-1.dll" + else + defines "DONUT_NO_VULKAN" + removefiles "src/platform/vulkan/**" + end filter "system:macosx" files "src/**.mm" diff --git a/src/core/application.cpp b/src/core/application.cpp index e53e420..de318fb 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -4,10 +4,13 @@ #include "settings_manager.h" #include "platform/opengl/opengl_device.h" -#include "platform/vulkan/vulkan_device.h" +#ifndef DONUT_NO_VULKAN + #include "platform/vulkan/vulkan_device.h" +#endif #include <GLFW/glfw3.h> #include <imgui.h> +#include <imgui_impl_glfw.h> // ImGui_ImplGlfw_GetContentScaleForWindow #include <glm/glm.hpp> #include <algorithm> #include <thread> @@ -29,6 +32,7 @@ namespace Donut } Application::Application(const std::string& name, int width, int height) + : m_title(name), m_init_w(width), m_init_h(height) { s_instance = this; @@ -42,11 +46,31 @@ namespace Donut ? RendererAPI::API::Vulkan : RendererAPI::API::OpenGL); } +#ifdef DONUT_NO_VULKAN + // generated without a Vulkan SDK, so the backend isn't in this binary at all + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + { + DONUT_WARN("built without the Vulkan backend, falling back to OpenGL"); + RendererAPI::set_api(RendererAPI::API::OpenGL); + } +#else + if (RendererAPI::get_api() == RendererAPI::API::Vulkan && !RHI::vulkan_runtime_available()) + { + DONUT_WARN("no Vulkan runtime on this machine (vulkan-1.dll), falling back to OpenGL"); + RendererAPI::set_api(RendererAPI::API::OpenGL); + } if (RendererAPI::get_api() == RendererAPI::API::Vulkan) RHI::vulkan_prepare_glfw(); // must precede glfwInit() inside the Window ctor +#endif m_window = create_scope<Window>(name, width, height); m_window->set_event_callback([this](Event& event) { on_event(event); }); + if (!m_window->get_native_window()) + { + DONUT_ERROR("no window, no app (GLFW said why above)"); + m_running = false; + return; + } on_init(); } @@ -62,15 +86,33 @@ namespace Donut int w = 0, h = 0; glfwGetFramebufferSize((GLFWwindow*)m_window->get_native_window(), &w, &h); +#ifndef DONUT_NO_VULKAN if (RendererAPI::get_api() == RendererAPI::API::Vulkan) m_device = RHI::create_vulkan_device(); else +#endif m_device = RHI::create_opengl_device(); RHI::NativeWindow native{ m_window->get_native_window(), w, h }; if (!m_device->init(native)) { + m_device->shutdown(); + m_device.reset(); +#ifndef DONUT_NO_VULKAN + // a Vulkan window can't host a GL context (GLFW_NO_API), so rebuild it + // and try once more on OpenGL. the saved setting stays as the user left it. + if (RendererAPI::get_api() == RendererAPI::API::Vulkan) + { + DONUT_WARN("Vulkan device init failed (see above), falling back to OpenGL"); + RendererAPI::set_api(RendererAPI::API::OpenGL); + m_window.reset(); + m_window = create_scope<Window>(m_title, m_init_w, m_init_h); + m_window->set_event_callback([this](Event& event) { on_event(event); }); + if (m_window->get_native_window()) { on_init(); return; } + } +#endif DONUT_ERROR("RHI device initialization failed"); + m_running = false; // nothing to draw with; run() just returns return; } @@ -80,9 +122,13 @@ namespace Donut m_ui = create_scope<UILayer>(); m_device->init_imgui(); + // windows hands out physical pixels with no scaling, so a 4k desktop at 200% + // would get a half-size ui. the glfw backend reports 1.0 on macos (retina + // scaling lives in the framebuffer there), so the mac is untouched. + const float dpi = ImGui_ImplGlfw_GetContentScaleForWindow((GLFWwindow*)m_window->get_native_window()); // the UI layer owns its typography and its imgui.ini section; both go into // the device-created ImGui context before its first NewFrame reads the ini. - m_ui->init(); + m_ui->init(dpi); g_prev_scroll = glfwSetScrollCallback((GLFWwindow*)m_window->get_native_window(), donut_scroll_callback); @@ -99,8 +145,8 @@ namespace Donut { double frame_start = glfwGetTime(); glfwPollEvents(); - if (!m_minimized && m_device) - render_frame(); + if (m_minimized) { glfwWaitEventsTimeout(0.1); continue; } // nothing to draw, so don't spin a core + if (m_device) render_frame(); // with vsync off, cap to the target FPS (vsync itself paces otherwise). if (!SettingsManager::get_v_sync_enabled()) @@ -179,7 +225,7 @@ namespace Donut { ExportConfig cfg; cfg.width = 1280; cfg.height = 720; - int written = export_frame(cfg); + [[maybe_unused]] const int written = export_frame(cfg); // only the debug log reads it DONUT_INFO("autoexport wrote {} file(s)", written); close(); } diff --git a/src/core/application.h b/src/core/application.h index bb88919..3c24ee8 100644 --- a/src/core/application.h +++ b/src/core/application.h @@ -69,6 +69,10 @@ namespace Donut bool m_running = true; bool m_minimized = false; + // kept for rebuilding the window when Vulkan fails and we retry on OpenGL + std::string m_title; + int m_init_w = 1280, m_init_h = 720; + static Application* s_instance; }; } diff --git a/src/core/log.h b/src/core/log.h index 4237d1c..ca96b26 100644 --- a/src/core/log.h +++ b/src/core/log.h @@ -141,16 +141,15 @@ namespace Donut }; } +// trace/info are debug-only chatter. warnings and errors stay in every build: +// a release build that fails on someone else's machine should still say why. #if defined(DONUT_DEBUG) #define DONUT_TRACE(format, ...) ::Donut::Logger::trace(format __VA_OPT__(,) __VA_ARGS__) #define DONUT_INFO(format, ...) ::Donut::Logger::info(format __VA_OPT__(,) __VA_ARGS__) - #define DONUT_WARN(format, ...) ::Donut::Logger::warn(format __VA_OPT__(,) __VA_ARGS__) - #define DONUT_ERROR(format, ...) ::Donut::Logger::error(format __VA_OPT__(,) __VA_ARGS__) - #define DONUT_FATAL(format, ...) ::Donut::Logger::fatal(format __VA_OPT__(,) __VA_ARGS__) #else #define DONUT_TRACE(format, ...) {} #define DONUT_INFO(format, ...) {} - #define DONUT_WARN(format, ...) {} - #define DONUT_ERROR(format, ...) {} - #define DONUT_FATAL(format, ...) {} #endif +#define DONUT_WARN(format, ...) ::Donut::Logger::warn(format __VA_OPT__(,) __VA_ARGS__) +#define DONUT_ERROR(format, ...) ::Donut::Logger::error(format __VA_OPT__(,) __VA_ARGS__) +#define DONUT_FATAL(format, ...) ::Donut::Logger::fatal(format __VA_OPT__(,) __VA_ARGS__) diff --git a/src/core/window.cpp b/src/core/window.cpp index 9a8d1ec..8bb4d7e 100644 --- a/src/core/window.cpp +++ b/src/core/window.cpp @@ -24,6 +24,8 @@ namespace Donut { DONUT_INFO("Initializing window: ", m_title, " (", m_width, "x", m_height, ")"); + glfwSetErrorCallback(glfw_error_callback); // before init, so a failing init says why + if (!s_glfw_initialized) { int success = glfwInit(); @@ -44,23 +46,26 @@ namespace Donut } else { -#ifdef __APPLE__ - // macOS caps OpenGL at 4.1 Core Profile and demands a forward-compat - // core-profile context for anything modern (>= 3.3) to even compile. - // skip these hints and GLFW hands you a dusty 2.1 context and every - // single shader in the project refuses to build. so, we ask nicely: + // the shaders are GLSL 4.10 core, so ask for exactly that everywhere. + // macOS caps OpenGL at 4.1 and also wants forward-compat for anything + // past 2.1, or every shader in the project refuses to build. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#ifdef __APPLE__ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); #endif } + // size the window in physical pixels for the monitor's scale (windows/x11; + // a no-op on macos, where the framebuffer scales instead) + glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); m_window = glfwCreateWindow(m_width, m_height, m_title.c_str(), nullptr, nullptr); if (!m_window) { DONUT_ERROR("Could not create GLFW window!"); glfwTerminate(); + s_glfw_initialized = false; return; } @@ -70,7 +75,6 @@ namespace Donut glfwMakeContextCurrent(m_window); glfwSetWindowUserPointer(m_window, this); - glfwSetErrorCallback(glfw_error_callback); glfwSetWindowCloseCallback(m_window, glfw_window_close_callback); glfwSetWindowSizeCallback(m_window, glfw_window_size_callback); glfwSetWindowFocusCallback(m_window, glfw_window_focus_callback); @@ -87,6 +91,7 @@ namespace Donut auto Window::shutdown() -> void { DONUT_INFO("Shutting down window: ", m_title); + if (!m_window) return; // creation failed, and init() already cleaned up GLFW glfwDestroyWindow(m_window); s_glfw_window_count--; diff --git a/src/core/window.h b/src/core/window.h index b8b59c5..b97874d 100644 --- a/src/core/window.h +++ b/src/core/window.h @@ -53,7 +53,7 @@ namespace Donut static auto glfw_cursor_pos_callback(GLFWwindow* window, double x_pos, double y_pos) -> void; private: - GLFWwindow* m_window; + GLFWwindow* m_window = nullptr; EventHandler m_event_handler; std::string m_title; diff --git a/src/main.cpp b/src/main.cpp index a59cf8b..c813278 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,30 @@ #include "core/application.h" -int main() +#include <filesystem> + +// every asset path is relative to the repo root, but a double-clicked exe (or a +// ./bin/<cfg>/donut from elsewhere) starts wherever the launcher put it. walk up +// from `start` until assets/shaders shows up and run from there. +static auto chdir_to_project_root(std::filesystem::path start) -> bool +{ + namespace fs = std::filesystem; + std::error_code ec; + for (fs::path p = start; !p.empty(); p = p.parent_path()) + { + if (fs::exists(p / "assets" / "shaders", ec)) { fs::current_path(p, ec); return !ec; } + if (p == p.root_path()) break; + } + return false; +} + +int main(int /*argc*/, char** argv) { + // the cwd first (the normal case), then wherever the exe itself lives. if both + // miss, the loaders complain with the cwd in the message. + std::error_code ec; + if (!chdir_to_project_root(std::filesystem::current_path(ec)) && argv && argv[0]) + chdir_to_project_root(std::filesystem::absolute(argv[0], ec).parent_path()); Donut::Application* app = new Donut::Application("Donut Engine - Black Hole Simulation", 1280, 720); app->run(); delete app; -}
\ No newline at end of file +} diff --git a/src/platform/opengl/shader.cpp b/src/platform/opengl/shader.cpp index ca63c14..7319f8b 100644 --- a/src/platform/opengl/shader.cpp +++ b/src/platform/opengl/shader.cpp @@ -6,6 +6,8 @@ #include <fstream> #include <iostream> #include <vector> +#include <filesystem> +#include "core/log.h" namespace Donut { @@ -93,6 +95,13 @@ namespace Donut in.read(&result[0], size); } } + else + { + std::error_code ec; // u8string: msvc's string() throws on a cwd outside the ANSI code page + const auto cwd = std::filesystem::current_path(ec).u8string(); + DONUT_ERROR("shader file not found: {} (cwd {}; generated shaders live in assets/shaders/generated)", + filepath, std::string(cwd.begin(), cwd.end())); + } return result; } @@ -140,7 +149,7 @@ namespace Donut int max_length = 0; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &max_length); std::vector<char> info_log(max_length); - glGetShaderInfoLog(shader, max_length, &max_length, &info_log[0]); + if (max_length > 0) glGetShaderInfoLog(shader, max_length, &max_length, info_log.data()); // [0] on an empty vector trips msvc's debug STL glDeleteShader(shader); for (auto id : glShaderIDs) glDeleteShader(id); @@ -167,7 +176,7 @@ namespace Donut int max_length = 0; glGetProgramiv(m_renderer_id, GL_INFO_LOG_LENGTH, &max_length); std::vector<char> info_log(max_length); - glGetProgramInfoLog(m_renderer_id, max_length, &max_length, &info_log[0]); + if (max_length > 0) glGetProgramInfoLog(m_renderer_id, max_length, &max_length, info_log.data()); glDeleteProgram(m_renderer_id); for (auto id : glShaderIDs) glDeleteShader(id); diff --git a/src/platform/opengl/texture.cpp b/src/platform/opengl/texture.cpp index 5036ca6..41d14d5 100644 --- a/src/platform/opengl/texture.cpp +++ b/src/platform/opengl/texture.cpp @@ -188,15 +188,15 @@ namespace Donut glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - auto equirect_shader = Shader::create("assets/shaders/EquirectToCubemap.glsl"); - if (!equirect_shader) + // the slang-generated capture shader, the same source the vulkan path uses + auto equirect_shader = Shader::create("assets/shaders/generated/equirect_to_cubemap.glsl"); + uint32_t shader_program = equirect_shader ? equirect_shader->get_renderer_id() : 0; + if (!shader_program) { - DONUT_ERROR("Failed to create equirectangular to cubemap shader"); + DONUT_ERROR("equirect_to_cubemap shader missing or failed to compile; the sky stays dark"); return; } - uint32_t shader_program = equirect_shader->get_renderer_id(); - float vertices[] = { -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, @@ -231,7 +231,7 @@ namespace Donut glUniform1i(glGetUniformLocation(shader_program, "u_EquirectangularMap"), 0); // EquirectToCubemap is authored in Slang (row-major); transpose glm's // column-major matrices on upload (GL_TRUE) to match. - glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_Projection"), 1, GL_TRUE, &capture_projection[0][0]); + glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_projection"), 1, GL_TRUE, &capture_projection[0][0]); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, hdr_texture); @@ -239,7 +239,7 @@ namespace Donut glBindFramebuffer(GL_FRAMEBUFFER, capture_fbo); for (unsigned int i = 0; i < 6; ++i) { - glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_View"), 1, GL_TRUE, &capture_views[i][0][0]); + glUniformMatrix4fv(glGetUniformLocation(shader_program, "u_view"), 1, GL_TRUE, &capture_views[i][0][0]); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, m_renderer_id, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glBindVertexArray(cube_vao); diff --git a/src/platform/vulkan/vulkan_common.h b/src/platform/vulkan/vulkan_common.h index 22d389c..ead4d1f 100644 --- a/src/platform/vulkan/vulkan_common.h +++ b/src/platform/vulkan/vulkan_common.h @@ -187,6 +187,9 @@ namespace Donut::RHI auto bind_pipeline(Pipeline* p) -> void override { m_pipe = static_cast<VkPipelineR*>(p); + // a pipeline whose shaders never loaded has no vk object; binding a null + // handle is driver roulette, so skip it and let draw() skip too + if (!m_pipe || m_pipe->m_pipeline == VK_NULL_HANDLE) { m_pipe = nullptr; return; } vkCmdBindPipeline(m_cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipe->m_pipeline); } auto set_viewport(int x, int y, int w, int h, bool flip_y) -> void override @@ -216,9 +219,9 @@ namespace Donut::RHI auto bind_index_buffer(Buffer* ib) -> void override { vkCmdBindIndexBuffer(m_cmd, static_cast<VkBufferR*>(ib)->m_buf, 0, VK_INDEX_TYPE_UINT32); } auto draw(uint32_t vertex_count) -> void override - { flush_descriptors(); vkCmdDraw(m_cmd, vertex_count, 1, 0, 0); } + { if (!m_pipe) return; flush_descriptors(); vkCmdDraw(m_cmd, vertex_count, 1, 0, 0); } auto draw_indexed(uint32_t index_count) -> void override - { flush_descriptors(); vkCmdDrawIndexed(m_cmd, index_count, 1, 0, 0, 0); } + { if (!m_pipe) return; flush_descriptors(); vkCmdDrawIndexed(m_cmd, index_count, 1, 0, 0, 0); } // allocate + write + bind a descriptor set for the current pipeline's // declared resources, using whatever was bound since bind_pipeline. diff --git a/src/platform/vulkan/vulkan_cubemap.cpp b/src/platform/vulkan/vulkan_cubemap.cpp index 3ffc496..de0116e 100644 --- a/src/platform/vulkan/vulkan_cubemap.cpp +++ b/src/platform/vulkan/vulkan_cubemap.cpp @@ -2,6 +2,7 @@ #include "stb_image.h" #include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/packing.hpp> namespace Donut::RHI { @@ -36,10 +37,22 @@ namespace Donut::RHI csm.addressModeU = csm.addressModeV = csm.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; vkCreateSampler(m_device, &csm, nullptr, &tex->m_sampler); + // the capture shaders come first: if they're missing (no generated/ dir) + // take the same dark road as a missing hdr, rather than handing the driver + // two garbage module handles. + VkShaderModule vmod = VK_NULL_HANDLE, fmod = VK_NULL_HANDLE; + const bool shaders_ok = create_shader_module("assets/shaders/generated/equirect_to_cubemap.vertexMain.spv", vmod) + && create_shader_module("assets/shaders/generated/equirect_to_cubemap.fragmentMain.spv", fmod); + // bottom row first, same as the GL loader: SampleSphericalMap sends "up" to + // v = 1, so the top of the panorama has to be the last row. without this the + // whole sky is upside down (the old projection flip was half-hiding that). int w = 0, h = 0, ch = 0; - float* pixels = stbi_loadf(path.c_str(), &w, &h, &ch, 4); + stbi_set_flip_vertically_on_load(true); + float* pixels = shaders_ok ? stbi_loadf(path.c_str(), &w, &h, &ch, 4) : nullptr; if (!pixels) { + if (vmod) vkDestroyShaderModule(m_device, vmod, nullptr); + if (fmod) vkDestroyShaderModule(m_device, fmod, nullptr); DONUT_WARN("Vulkan RHI: HDRI '{}' could not be loaded; using a dark background", path); VkCommandBufferAllocateInfo cbai{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO }; cbai.commandPool = m_command_pool; cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; cbai.commandBufferCount = 1; @@ -65,13 +78,14 @@ namespace Donut::RHI } const VkFormat eq_fmt = VK_FORMAT_R16G16B16A16_SFLOAT; - size_t texel_count = (size_t)w * h * 4; + size_t texel_count = (size_t)w * (size_t)h * 4; VkDeviceSize eq_size = (VkDeviceSize)texel_count * sizeof(uint16_t); VkBuffer eq_staging; VkDeviceMemory eq_staging_mem; create_buffer_raw(eq_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, host_vis, eq_staging, eq_staging_mem); void* mp = nullptr; vkMapMemory(m_device, eq_staging_mem, 0, eq_size, 0, &mp); uint16_t* dst = (uint16_t*)mp; - for (size_t i = 0; i < texel_count; ++i) { __fp16 hf = (__fp16)pixels[i]; std::memcpy(&dst[i], &hf, sizeof(uint16_t)); } + // glm's half packing rather than __fp16: that one is a clang/ARM extension and MSVC has never heard of it + for (size_t i = 0; i < texel_count; ++i) dst[i] = glm::packHalf1x16(pixels[i]); vkUnmapMemory(m_device, eq_staging_mem); stbi_image_free(pixels); @@ -141,9 +155,6 @@ namespace Donut::RHI VkDescriptorPoolCreateInfo dpci{ VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO }; dpci.maxSets = 6; dpci.poolSizeCount = 2; dpci.pPoolSizes = psizes; vkCreateDescriptorPool(m_device, &dpci, nullptr, &pool); - VkShaderModule vmod, fmod; - create_shader_module("assets/shaders/generated/equirect_to_cubemap.vertexMain.spv", vmod); - create_shader_module("assets/shaders/generated/equirect_to_cubemap.fragmentMain.spv", fmod); VkPipelineLayout playout; VkPipelineLayoutCreateInfo plci{ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO }; plci.setLayoutCount = 1; plci.pSetLayouts = &set_layout; vkCreatePipelineLayout(m_device, &plci, nullptr, &playout); @@ -180,8 +191,11 @@ namespace Donut::RHI create_buffer_raw(sizeof(cube_verts), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, host_vis, cube_vb, cube_vb_mem); vkMapMemory(m_device, cube_vb_mem, 0, sizeof(cube_verts), 0, &mp); std::memcpy(mp, cube_verts, sizeof(cube_verts)); vkUnmapMemory(m_device, cube_vb_mem); + // no vulkan y-flip here. rendering into a texture and sampling it back is + // the same in both APIs (clip-space bottom lands on row 0 either way), so + // these are the exact GL capture matrices. the flip that used to sit here + // mirrored every face top to bottom and scrambled the sky. glm::mat4 proj = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); - proj[1][1] *= -1.0f; glm::mat4 views[6] = { glm::lookAt(glm::vec3(0), glm::vec3( 1, 0, 0), glm::vec3(0, -1, 0)), glm::lookAt(glm::vec3(0), glm::vec3(-1, 0, 0), glm::vec3(0, -1, 0)), @@ -194,7 +208,10 @@ namespace Donut::RHI for (uint32_t i = 0; i < 6; ++i) { create_buffer_raw(128, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, host_vis, ubo[i], ubo_mem[i]); - glm::mat4 mats[2] = { glm::transpose(proj), glm::transpose(views[i]) }; + // straight glm, no transpose: the SPIR-V marks these RowMajor and does + // v * M, which reads glm's column-major memory as-is (same as every + // other UBO here). only GL's flattened loose uniforms need the transpose. + glm::mat4 mats[2] = { proj, views[i] }; vkMapMemory(m_device, ubo_mem[i], 0, 128, 0, &mp); std::memcpy(mp, mats, 128); vkUnmapMemory(m_device, ubo_mem[i]); VkDescriptorSetAllocateInfo dsai{ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO }; dsai.descriptorPool = pool; dsai.descriptorSetCount = 1; dsai.pSetLayouts = &set_layout; vkAllocateDescriptorSets(m_device, &dsai, &sets[i]); diff --git a/src/platform/vulkan/vulkan_device.cpp b/src/platform/vulkan/vulkan_device.cpp index f780f6f..54a4a3c 100644 --- a/src/platform/vulkan/vulkan_device.cpp +++ b/src/platform/vulkan/vulkan_device.cpp @@ -1,5 +1,15 @@ #include "vulkan_common.h" +#include <algorithm> +#include <filesystem> + +#if defined(_WIN32) + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include <windows.h> // LoadLibraryExW, for the runtime probe +#endif + #include <imgui.h> #include <imgui_impl_glfw.h> #include <imgui_impl_vulkan.h> @@ -44,7 +54,16 @@ namespace Donut::RHI auto VulkanDevice::create_shader_module(const std::string& path, VkShaderModule& out) const -> bool { auto spv = load_spirv(path); - if (spv.empty()) { DONUT_ERROR("Vulkan RHI: failed to load SPIR-V {}", path); return false; } + if (spv.empty()) + { + // u8string, not string(): msvc's narrow conversion throws on a cwd the + // ANSI code page can't spell, which would turn this log into a crash + std::error_code ec; + const auto cwd = std::filesystem::current_path(ec).u8string(); + DONUT_ERROR("Vulkan RHI: failed to load SPIR-V {} (cwd {}; shaders live in assets/shaders/generated, see tools/compile-shaders.sh)", + path, std::string(cwd.begin(), cwd.end())); + return false; + } VkShaderModuleCreateInfo ci{ VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO }; ci.codeSize = spv.size() * 4; ci.pCode = spv.data(); return vkCreateShaderModule(m_device, &ci, nullptr, &out) == VK_SUCCESS; @@ -190,8 +209,15 @@ namespace Donut::RHI vkQueueSubmit(m_graphics_queue, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(m_graphics_queue); void* mp = nullptr; vkMapMemory(m_device, mem, 0, sz, 0, &mp); - std::memcpy(out.data(), mp, sz); // RGBA8_UNORM, top-down (top-left origin) + std::memcpy(out.data(), mp, sz); vkUnmapMemory(m_device, mem); + // off-screen targets hold the same rows as on GL: clip-space bottom lands on + // row 0 in both APIs, and the present pass flips it upright. so flip here + // too, exactly like the GL readback, or the export comes out upside down. + const size_t row = (size_t)w * 4; + for (uint32_t y = 0; y < h / 2; ++y) + std::swap_ranges(out.begin() + (std::ptrdiff_t)(y * row), out.begin() + (std::ptrdiff_t)((y + 1) * row), + out.begin() + (std::ptrdiff_t)((h - 1 - y) * row)); vkFreeCommandBuffers(m_device, m_command_pool, 1, &cmd); vkDestroyBuffer(m_device, buf, nullptr); vkFreeMemory(m_device, mem, nullptr); } @@ -227,8 +253,12 @@ namespace Donut::RHI vkQueueSubmit(m_graphics_queue, 1, &si, VK_NULL_HANDLE); vkQueueWaitIdle(m_graphics_queue); void* mp = nullptr; vkMapMemory(m_device, mem, 0, sz, 0, &mp); - std::memcpy(out.data(), mp, sz); // R32G32B32A32_SFLOAT, top-down + std::memcpy(out.data(), mp, sz); // R32G32B32A32_SFLOAT vkUnmapMemory(m_device, mem); + const size_t row = (size_t)w * 4; // same flip as the RGBA8 readback above + for (uint32_t y = 0; y < h / 2; ++y) + std::swap_ranges(out.begin() + (std::ptrdiff_t)(y * row), out.begin() + (std::ptrdiff_t)((y + 1) * row), + out.begin() + (std::ptrdiff_t)((h - 1 - y) * row)); vkFreeCommandBuffers(m_device, m_command_pool, 1, &cmd); vkDestroyBuffer(m_device, buf, nullptr); vkFreeMemory(m_device, mem, nullptr); } @@ -299,6 +329,16 @@ namespace Donut::RHI auto create_vulkan_device() -> Scope<Device> { return create_scope<VulkanDevice>(); } + auto vulkan_runtime_available() -> bool + { +#if defined(_WIN32) + // stays loaded on purpose: the delay-load thunks bind to this module later + return LoadLibraryExW(L"vulkan-1.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) != nullptr; +#else + return true; // macOS links the loader directly, and there's no Windows-style delay-load +#endif + } + auto vulkan_prepare_glfw() -> void { #ifdef __APPLE__ diff --git a/src/platform/vulkan/vulkan_device.h b/src/platform/vulkan/vulkan_device.h index 85cc5ca..31f5842 100644 --- a/src/platform/vulkan/vulkan_device.h +++ b/src/platform/vulkan/vulkan_device.h @@ -14,4 +14,8 @@ namespace Donut::RHI // the loader the app links against (its own dlopen fails on macOS/Homebrew) // and configures the MoltenVK ICD / layer paths. auto vulkan_prepare_glfw() -> void; + + // whether a Vulkan loader exists on this machine at all. on windows vulkan-1.dll + // is delay-loaded, so this has to say yes before any vk* call; true elsewhere. + auto vulkan_runtime_available() -> bool; } diff --git a/src/platform/vulkan/vulkan_resources.cpp b/src/platform/vulkan/vulkan_resources.cpp index ee2091b..05cb1be 100644 --- a/src/platform/vulkan/vulkan_resources.cpp +++ b/src/platform/vulkan/vulkan_resources.cpp @@ -77,7 +77,10 @@ namespace Donut::RHI VkImageCreateInfo ici{ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; ici.imageType = VK_IMAGE_TYPE_2D; ici.format = cfmt; ici.extent = { (uint32_t)w, (uint32_t)h, 1 }; ici.mipLevels = 1; ici.arrayLayers = 1; ici.samples = VK_SAMPLE_COUNT_1_BIT; - ici.tiling = VK_IMAGE_TILING_OPTIMAL; ici.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + // TRANSFER_SRC because export reads targets back with a copy. MoltenVK never + // minded it missing; a desktop driver is within its rights to hand back garbage. + ici.tiling = VK_IMAGE_TILING_OPTIMAL; + ici.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; vkCreateImage(m_device, &ici, nullptr, &rt->m_image); VkMemoryRequirements req{}; vkGetImageMemoryRequirements(m_device, rt->m_image, &req); VkMemoryAllocateInfo ai{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO }; diff --git a/src/platform/vulkan/vulkan_swapchain.cpp b/src/platform/vulkan/vulkan_swapchain.cpp index cbd4512..65b510c 100644 --- a/src/platform/vulkan/vulkan_swapchain.cpp +++ b/src/platform/vulkan/vulkan_swapchain.cpp @@ -11,19 +11,44 @@ namespace Donut::RHI const char** glfwExts = glfwGetRequiredInstanceExtensions(&glfwExtCount); if (!glfwExts) { DONUT_ERROR("Vulkan RHI: GLFW reports no surface support"); return false; } std::vector<const char*> exts(glfwExts, glfwExts + glfwExtCount); - exts.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - exts.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + // the portability bits are how MoltenVK gets listed at all (it's a + // "portability" driver, not a conformant one). a desktop loader may not + // offer the extension, and asking for one it doesn't have fails the whole + // instance, so only ask when it's on the menu. + uint32_t iec = 0; vkEnumerateInstanceExtensionProperties(nullptr, &iec, nullptr); + std::vector<VkExtensionProperties> iexts(iec); + vkEnumerateInstanceExtensionProperties(nullptr, &iec, iexts.data()); + auto has_ext = [&](const char* name) + { + for (const auto& e : iexts) if (std::strcmp(e.extensionName, name) == 0) return true; + return false; + }; + VkInstanceCreateFlags flags = 0; +#ifdef VK_KHR_portability_enumeration // older SDK headers don't have it at all + if (has_ext(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) + { + exts.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + } +#endif + if (has_ext(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) + exts.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + + // validation only in debug builds: it's slow, and with the SDK installed on + // windows it would otherwise load for every release run too. std::vector<const char*> layers; +#ifdef DONUT_DEBUG uint32_t layer_count = 0; vkEnumerateInstanceLayerProperties(&layer_count, nullptr); std::vector<VkLayerProperties> avail(layer_count); vkEnumerateInstanceLayerProperties(&layer_count, avail.data()); for (const auto& l : avail) if (std::strcmp(l.layerName, "VK_LAYER_KHRONOS_validation") == 0) layers.push_back("VK_LAYER_KHRONOS_validation"); +#endif VkInstanceCreateInfo ici{ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO }; - ici.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; + ici.flags = flags; ici.pApplicationInfo = &app; ici.enabledExtensionCount = (uint32_t)exts.size(); ici.ppEnabledExtensionNames = exts.data(); ici.enabledLayerCount = (uint32_t)layers.size(); ici.ppEnabledLayerNames = layers.data(); @@ -31,6 +56,7 @@ namespace Donut::RHI if (r != VK_SUCCESS && !layers.empty()) { DONUT_WARN("Vulkan RHI: validation layer unavailable, continuing without it"); + layers.clear(); ici.enabledLayerCount = 0; ici.ppEnabledLayerNames = nullptr; r = vkCreateInstance(&ici, nullptr, &m_instance); } @@ -46,19 +72,30 @@ namespace Donut::RHI if (count == 0) { DONUT_ERROR("Vulkan RHI: no physical devices"); return false; } std::vector<VkPhysicalDevice> devices(count); vkEnumeratePhysicalDevices(m_instance, &count, devices.data()); - m_physical = devices[0]; - uint32_t q = 0; vkGetPhysicalDeviceQueueFamilyProperties(m_physical, &q, nullptr); - std::vector<VkQueueFamilyProperties> qfams(q); - vkGetPhysicalDeviceQueueFamilyProperties(m_physical, &q, qfams.data()); - bool fg = false, fp = false; - for (uint32_t i = 0; i < q; ++i) + // pick the best gpu that can both draw and present: a discrete card over an + // integrated one (laptops tend to list the igpu first), never one that can't + // present to our surface. + int best = -1; + for (VkPhysicalDevice pd : devices) { - if (!fg && (qfams[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) { m_graphics_family = i; fg = true; } - VkBool32 present = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(m_physical, i, m_surface, &present); - if (!fp && present) { m_present_family = i; fp = true; } + uint32_t q = 0; vkGetPhysicalDeviceQueueFamilyProperties(pd, &q, nullptr); + std::vector<VkQueueFamilyProperties> qfams(q); + vkGetPhysicalDeviceQueueFamilyProperties(pd, &q, qfams.data()); + uint32_t gfam = UINT32_MAX, pfam = UINT32_MAX; + for (uint32_t i = 0; i < q; ++i) + { + if (gfam == UINT32_MAX && (qfams[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) gfam = i; + VkBool32 present = VK_FALSE; vkGetPhysicalDeviceSurfaceSupportKHR(pd, i, m_surface, &present); + if (pfam == UINT32_MAX && present) pfam = i; + } + if (gfam == UINT32_MAX || pfam == UINT32_MAX) continue; + VkPhysicalDeviceProperties pp{}; vkGetPhysicalDeviceProperties(pd, &pp); + const int score = pp.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? 3 + : pp.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU ? 2 : 1; + if (score > best) { best = score; m_physical = pd; m_graphics_family = gfam; m_present_family = pfam; } } - if (!fg || !fp) { DONUT_ERROR("Vulkan RHI: no graphics/present queue"); return false; } + if (best < 0) { DONUT_ERROR("Vulkan RHI: no gpu with both a graphics and a present queue"); return false; } std::vector<const char*> dev_exts = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; uint32_t dec = 0; vkEnumerateDeviceExtensionProperties(m_physical, nullptr, &dec, nullptr); @@ -295,14 +332,26 @@ namespace Donut::RHI auto VulkanDevice::recreate_swapchain() -> bool { + // minimized (0x0 on windows): wait it out, but not through a close request, + // or closing a minimized window from the taskbar hangs the process int w = 0, h = 0; glfwGetFramebufferSize(m_window, &w, &h); - while (w == 0 || h == 0) { glfwGetFramebufferSize(m_window, &w, &h); glfwWaitEvents(); } + while ((w == 0 || h == 0) && !glfwWindowShouldClose(m_window)) { glfwWaitEvents(); glfwGetFramebufferSize(m_window, &w, &h); } + if (w == 0 || h == 0) return false; // closing; keep the old swapchain until shutdown m_width = w; m_height = h; vkDeviceWaitIdle(m_device); cleanup_swapchain(); if (!create_swapchain()) return false; if (!create_image_views()) return false; if (!create_depth_and_framebuffers())return false; + // a new swapchain can come back with a different image count, and the + // present semaphores are per image + if (m_render_finished.size() != m_images.size()) + { + for (auto s : m_render_finished) vkDestroySemaphore(m_device, s, nullptr); + m_render_finished.assign(m_images.size(), VK_NULL_HANDLE); + VkSemaphoreCreateInfo sci{ VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO }; + for (auto& s : m_render_finished) VKD_CHECK(vkCreateSemaphore(m_device, &sci, nullptr, &s)); + } m_images_in_flight.assign(m_images.size(), VK_NULL_HANDLE); return true; } diff --git a/src/ui/ui_layer.cpp b/src/ui/ui_layer.cpp index 90ddb7c..a65000e 100644 --- a/src/ui/ui_layer.cpp +++ b/src/ui/ui_layer.cpp @@ -140,7 +140,7 @@ namespace Donut // the theme: soft-rounded cards on a near-black ground, 1px borders for edges, // and the accent reserved for "this is live / selected" — slider grabs, checks, // the active tab's underline, the one primary button per panel. - static auto apply_donut_style() -> void + static auto apply_donut_style(float dpi_scale) -> void { ImGuiStyle& s = ImGui::GetStyle(); s.WindowRounding = 8.0f; s.ChildRounding = 6.0f; s.FrameRounding = 6.0f; @@ -216,6 +216,9 @@ namespace Donut c[ImGuiCol_TableRowBgAlt] = ImVec4(1.0f, 1.0f, 1.0f, 0.025f); c[ImGuiCol_TextSelectedBg] = amber_dim; c[ImGuiCol_DragDropTarget] = kAmberHi; + + // everything above is in 1x pixels; a 200% desktop wants it all doubled. + if (dpi_scale != 1.0f) { s.ScaleAllSizes(dpi_scale); s.FontScaleDpi = dpi_scale; } } // inspector row: label in a fixed left column, the control fills the rest. @@ -262,8 +265,9 @@ namespace Donut } } - auto UILayer::init() -> void + auto UILayer::init(float dpi_scale) -> void { + m_dpi_scale = dpi_scale; ImGuiIO& io = ImGui::GetIO(); m_font_body = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-Regular.ttf", kBodySize); m_font_heading = io.Fonts->AddFontFromFileTTF("assets/fonts/inter/static/Inter_18pt-SemiBold.ttf", kHeadSize); @@ -329,7 +333,7 @@ namespace Donut auto UILayer::draw(const UIContext& ctx) -> View { static bool styled = false; - if (!styled) { apply_donut_style(); styled = true; } + if (!styled) { apply_donut_style(m_dpi_scale); styled = true; } ImGuizmo::BeginFrame(); // the Properties panel draws the scene gizmo @@ -991,7 +995,11 @@ namespace Donut }; char buf[64]; row("Device", ctx.device_name.empty() ? "GPU" : ctx.device_name.c_str()); +#ifdef __APPLE__ row("API", RendererAPI::get_api() == RendererAPI::API::Vulkan ? "Vulkan (MoltenVK)" : "OpenGL 4.1"); +#else + row("API", RendererAPI::get_api() == RendererAPI::API::Vulkan ? "Vulkan" : "OpenGL"); +#endif row("Workspace", tab.label); std::snprintf(buf, sizeof(buf), "%.0f x %.0f", io.DisplaySize.x * io.DisplayFramebufferScale.x, io.DisplaySize.y * io.DisplayFramebufferScale.y); diff --git a/src/ui/ui_layer.h b/src/ui/ui_layer.h index 4479844..bcfff23 100644 --- a/src/ui/ui_layer.h +++ b/src/ui/ui_layer.h @@ -30,7 +30,7 @@ namespace Donut // loads the Inter faces and registers the workspace ini section on the // live ImGui context. call once after the device's init_imgui() and before // the first draw(): ImGui reads imgui.ini on its first NewFrame. - auto init() -> void; + auto init(float dpi_scale = 1.0f) -> void; auto draw(const UIContext& ctx) -> View; private: @@ -76,6 +76,7 @@ namespace Donut int m_active_tab = 0; // index into m_tabs; the source of truth int m_tab_request = -1; // one-frame programmatic strip selection bool m_first_frame = true; + float m_dpi_scale = 1.0f; // monitor content scale (windows/linux); 1 on macos // properties-panel gizmo op (0 = translate, 1 = scale); cast in the .cpp. int m_gizmo_op = 0; diff --git a/tools/compile-shaders.sh b/tools/compile-shaders.sh index 5da5d9a..a5d786e 100755 --- a/tools/compile-shaders.sh +++ b/tools/compile-shaders.sh @@ -14,8 +14,15 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" -SLANGC="${SLANGC:-$ROOT/tools/slang/bin/slangc}" -SPIRV_CROSS="${SPIRV_CROSS:-$(command -v spirv-cross || echo spirv-cross)}" +# the vulkan sdk ships both tools (git bash on windows); tools/slang + brew otherwise. +if [ -z "${SLANGC:-}" ]; then + if [ -n "${VULKAN_SDK:-}" ] && [ -x "$VULKAN_SDK/Bin/slangc.exe" ]; then SLANGC="$VULKAN_SDK/Bin/slangc.exe" + else SLANGC="$ROOT/tools/slang/bin/slangc"; fi +fi +if [ -z "${SPIRV_CROSS:-}" ]; then + if [ -n "${VULKAN_SDK:-}" ] && [ -x "$VULKAN_SDK/Bin/spirv-cross.exe" ]; then SPIRV_CROSS="$VULKAN_SDK/Bin/spirv-cross.exe" + else SPIRV_CROSS="$(command -v spirv-cross || echo spirv-cross)"; fi +fi SRC="$ROOT/assets/shaders" OUT="$SRC/generated" diff --git a/tools/fetch-slang.sh b/tools/fetch-slang.sh index 5229bf7..d94a63b 100755 --- a/tools/fetch-slang.sh +++ b/tools/fetch-slang.sh @@ -16,6 +16,10 @@ case "$(uname -s)-$(uname -m)" in Darwin-x86_64) ASSET="macos-x86_64" ;; Linux-x86_64) ASSET="linux-x86_64" ;; Linux-aarch64|Linux-arm64) ASSET="linux-aarch64" ;; + MINGW*|MSYS*|CYGWIN*) + # git bash on windows: the Vulkan SDK already ships slangc.exe and + # spirv-cross.exe, and compile-shaders.sh picks them up from $VULKAN_SDK/Bin + echo "fetch-slang: nothing to fetch on windows, the Vulkan SDK's slangc.exe is used"; exit 0 ;; *) echo "fetch-slang: unsupported platform $(uname -s)-$(uname -m)"; exit 1 ;; esac |
