diff options
| -rw-r--r-- | Assets/Shaders/Geodesic.glsl | 258 | ||||
| -rw-r--r-- | premake5.lua | 16 | ||||
| -rw-r--r-- | src/Core/Window.cpp | 24 | ||||
| -rw-r--r-- | src/Engine/Engine.cpp | 130 | ||||
| -rw-r--r-- | src/Engine/Engine.h | 21 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLFramebuffer.cpp | 23 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLIndexBuffer.cpp | 2 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLRendererAPI.cpp | 5 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLShader.cpp | 23 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLShader.h | 2 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLTexture.cpp | 113 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLVertexArray.cpp | 6 | ||||
| -rw-r--r-- | src/Platform/OpenGL/OpenGLVertexBuffer.cpp | 2 |
13 files changed, 414 insertions, 211 deletions
diff --git a/Assets/Shaders/Geodesic.glsl b/Assets/Shaders/Geodesic.glsl index 29f7b1c..cb435d0 100644 --- a/Assets/Shaders/Geodesic.glsl +++ b/Assets/Shaders/Geodesic.glsl @@ -1,9 +1,37 @@ -#version 430 -layout(local_size_x = 16, local_size_y = 16) in; +// Geodesic ray tracer. +// +// Originally an OpenGL 4.3 compute shader that wrote its result with +// imageStore(). macOS OpenGL is frozen at 4.1 and has neither compute shaders +// nor image load/store, so this is expressed as a fullscreen vertex+fragment +// pass (GLSL 4.10) that renders into an FBO colour attachment instead. Each +// fragment does the work one compute invocation used to: gl_FragCoord replaces +// gl_GlobalInvocationID, and the shading maths below is unchanged. -layout(binding = 0, rgba8) writeonly uniform image2D outImage; -layout(binding = 5) uniform samplerCube u_HDRIEnvironment; -layout(std140, binding = 1) uniform Camera +#type vertex +#version 410 core + +layout(location = 0) in vec2 a_Position; + +void main() +{ + gl_Position = vec4(a_Position, 0.0, 1.0); +} + +#type fragment +#version 410 core + +out vec4 fragColor; + +// Compute-resolution in pixels; supplied by the host each frame (replaces the +// compute shader's imageSize(outImage)). +uniform vec2 u_Resolution; + +// GLSL 4.10 does not allow explicit binding qualifiers on uniform blocks or +// samplers; the host associates these with binding points via +// glUniformBlockBinding / glUniform1i. +uniform samplerCube u_HDRIEnvironment; + +layout(std140) uniform Camera { vec3 camPos; float _pad0; vec3 camRight; float _pad1; @@ -15,7 +43,7 @@ layout(std140, binding = 1) uniform Camera int _pad4; } cam; -layout(std140, binding = 2) uniform Disk +layout(std140) uniform Disk { float disk_r1; float disk_r2; @@ -24,15 +52,15 @@ layout(std140, binding = 2) uniform Disk float disk_density; }; -layout(std140, binding = 3) uniform Objects +layout(std140) uniform Objects { int numObjects; vec4 objPosRadius[16]; vec4 objColor[16]; - float mass[16]; + float mass[16]; }; -layout(std140, binding = 4) uniform Simulation +layout(std140) uniform Simulation { int maxStepsMoving; int maxStepsStatic; @@ -42,7 +70,7 @@ layout(std140, binding = 4) uniform Simulation const float SagA_rs = 1.269e10; const float D_LAMBDA = 1e7; -const double ESCAPE_R = 1e30; +const float ESCAPE_R = 1e30; const int DEFAULT_MAX_STEPS_MOVING = 12000; const int DEFAULT_MAX_STEPS_STATIC = 8000; @@ -61,7 +89,7 @@ vec3 SampleHDRI(vec3 direction) return texture(u_HDRIEnvironment, direction).rgb; } -float hash(float p) +float hash(float p) { p = fract(p * 0.1031); p *= p + 33.33; @@ -69,27 +97,27 @@ float hash(float p) return fract(p); } -float hash(vec2 p) +float hash(vec2 p) { vec3 p3 = fract(vec3(p.xyx) * vec3(0.1031, 0.1030, 0.0973)); p3 += dot(p3, p3.yzx + 33.33); return fract((p3.x + p3.y) * p3.z); } -float hash(vec3 p) +float hash(vec3 p) { p = fract(p * vec3(0.1031, 0.1030, 0.0973)); p += dot(p, p.yxz + 33.33); return fract((p.x + p.y) * p.z); } -float noise(vec3 x) +float noise(vec3 x) { vec3 i = floor(x); vec3 frac = fract(x); - + vec3 u = frac * frac * (3.0 - 2.0 * frac); - + float a = hash(i); float b = hash(i + vec3(1.0, 0.0, 0.0)); float c = hash(i + vec3(0.0, 1.0, 0.0)); @@ -98,19 +126,19 @@ float noise(vec3 x) float f = hash(i + vec3(1.0, 0.0, 1.0)); float g = hash(i + vec3(0.0, 1.0, 1.0)); float h = hash(i + vec3(1.0, 1.0, 1.0)); - + return mix(mix(mix(a, b, u.x), mix(c, d, u.x), u.y), mix(mix(e, f, u.x), mix(g, h, u.x), u.y), u.z); } -float fbm(vec3 x, int octaves) +float fbm(vec3 x, int octaves) { float v = 0.0; float a = 0.5; float f = 1.0; vec3 shift = vec3(100, 200, 300); - - for (int i = 0; i < octaves; ++i) + + for (int i = 0; i < octaves; ++i) { v += a * noise(x * f); x = x * 2.0 + shift; @@ -120,54 +148,54 @@ float fbm(vec3 x, int octaves) return v; } -float GetCloudDensity(vec3 pos) +float GetCloudDensity(vec3 pos) { float r_cyl = length(vec2(pos.x, pos.z)); float r_norm = (r_cyl - disk_r1) / (disk_r2 - disk_r1); - - if (r_norm < 0.0 || r_norm > 1.0) + + if (r_norm < 0.0 || r_norm > 1.0) return 0.0; - + float h_norm = abs(pos.y) / thickness; float vertical_falloff = exp(-h_norm * h_norm * 3.0); float radial_density = 1.0 - r_norm * 0.5; - + vec3 noise_pos = pos * 1e-10; float keplerian_speed = 1.0 / sqrt(r_norm + 0.1); - + float rotation_angle = time * keplerian_speed * 0.5; vec3 rotated_pos = vec3( pos.x * cos(rotation_angle) - pos.z * sin(rotation_angle), pos.y, pos.x * sin(rotation_angle) + pos.z * cos(rotation_angle) ) * 1e-10; - + float large_turbulence = fbm(rotated_pos * 1.2, 5); - + float medium_wisps = fbm(rotated_pos * 2.5, 4); float small_detail = fbm(rotated_pos * 6.0, 3); float fine_detail = fbm(rotated_pos * 10.0, 2); - - float noise_mask = large_turbulence * 0.4 + - medium_wisps * 0.3 + - small_detail * 0.2 + + + float noise_mask = large_turbulence * 0.4 + + medium_wisps * 0.3 + + small_detail * 0.2 + fine_detail * 0.1; - + noise_mask = smoothstep(0.25, 0.75, noise_mask); - + float angle = atan(pos.z, pos.x); float rotated_angle = angle + time * 0.5; - + float spiral_arms = sin(rotated_angle * 3.0 + r_norm * 15.0) * 0.15 + 0.85; - + float orbital_angle = angle + time * keplerian_speed * 0.8; float orbital_pattern = sin(orbital_angle * 2.0 + r_norm * 8.0) * 0.2 + 0.8; - + float density = vertical_falloff * radial_density * noise_mask * spiral_arms * orbital_pattern; return density * disk_density; } -struct Ray +struct Ray { float x, y, z; float r, theta, phi; @@ -175,11 +203,11 @@ struct Ray float E, L; }; -Ray InitRay(vec3 pos, vec3 dir) +Ray InitRay(vec3 pos, vec3 dir) { Ray ray; - ray.x = pos.x; - ray.y = pos.y; + ray.x = pos.x; + ray.y = pos.y; ray.z = pos.z; ray.r = length(pos); ray.theta = acos(pos.z / ray.r); @@ -189,21 +217,21 @@ Ray InitRay(vec3 pos, vec3 dir) float dy = dir.y; float dz = dir.z; - ray.dr = sin(ray.theta)*cos(ray.phi)*dx + - sin(ray.theta)*sin(ray.phi)*dy + + ray.dr = sin(ray.theta)*cos(ray.phi)*dx + + sin(ray.theta)*sin(ray.phi)*dy + cos(ray.theta)*dz; - ray.dtheta = (cos(ray.theta)*cos(ray.phi)*dx + - cos(ray.theta)*sin(ray.phi)*dy - + ray.dtheta = (cos(ray.theta)*cos(ray.phi)*dx + + cos(ray.theta)*sin(ray.phi)*dy - sin(ray.theta)*dz) / ray.r; - ray.dphi = (-sin(ray.phi)*dx + cos(ray.phi)*dy) / + ray.dphi = (-sin(ray.phi)*dx + cos(ray.phi)*dy) / (ray.r * sin(ray.theta)); ray.L = ray.r * ray.r * sin(ray.theta) * ray.dphi; float f = 1.0 - SagA_rs / ray.r; - float dt_dL = sqrt((ray.dr*ray.dr)/f + - ray.r*ray.r*(ray.dtheta*ray.dtheta + + float dt_dL = sqrt((ray.dr*ray.dr)/f + + ray.r*ray.r*(ray.dtheta*ray.dtheta + sin(ray.theta)*sin(ray.theta)* ray.dphi*ray.dphi)); ray.E = f * dt_dL; @@ -211,22 +239,22 @@ Ray InitRay(vec3 pos, vec3 dir) return ray; } -bool Intercept(Ray ray, float rs) +bool Intercept(Ray ray, float rs) { return ray.r <= rs; } -bool InterceptObject(Ray ray) +bool InterceptObject(Ray ray) { vec3 P = vec3(ray.x, ray.y, ray.z); - - for (int i = 0; i < numObjects; ++i) + + for (int i = 0; i < numObjects; ++i) { vec3 center = objPosRadius[i].xyz; float radius = objPosRadius[i].w; - + float distSq = dot(P - center, P - center); - if (distSq > radius * radius * 4.0) + if (distSq > radius * radius * 4.0) continue; if (distSq <= radius * radius) @@ -241,7 +269,7 @@ bool InterceptObject(Ray ray) return false; } -void GeodesicRHS(Ray ray, out vec3 d1, out vec3 d2) +void GeodesicRHS(Ray ray, out vec3 d1, out vec3 d2) { float r = ray.r; float theta = ray.theta; @@ -259,7 +287,7 @@ void GeodesicRHS(Ray ray, out vec3 d1, out vec3 d2) d2.z = -2.0*dr*dphi/r - 2.0*cos(theta)/(sin(theta)) * dtheta * dphi; } -void RK4Step(inout Ray ray, float dL) +void RK4Step(inout Ray ray, float dL) { vec3 k1a, k1b; GeodesicRHS(ray, k1a, k1b); @@ -276,7 +304,7 @@ void RK4Step(inout Ray ray, float dL) ray.z = ray.r * cos(ray.theta); } -bool IsInDiskVolume(vec3 pos) +bool IsInDiskVolume(vec3 pos) { float r_cyl = length(vec2(pos.x, pos.z)); return (r_cyl >= disk_r1 && r_cyl <= disk_r2 && abs(pos.y) <= thickness); @@ -286,83 +314,79 @@ vec4 SampleDiskColor(vec3 pos) { float r_cyl = length(vec2(pos.x, pos.z)); float r_norm = (r_cyl - disk_r1) / (disk_r2 - disk_r1); - + vec3 innerColor = vec3(1.0, 0.9, 0.5); vec3 midColor = vec3(1.0, 0.6, 0.2); vec3 outerColor = vec3(0.9, 0.3, 0.1); - + vec3 baseColor; if (r_norm < 0.5) baseColor = mix(innerColor, midColor, r_norm * 2.0); else baseColor = mix(midColor, outerColor, (r_norm - 0.5) * 2.0); - + float r_norm_rot = (r_cyl - disk_r1) / (disk_r2 - disk_r1); float keplerian_speed = 1.0 / sqrt(r_norm_rot + 0.1); - + vec3 noise_pos = pos * 1e-10; - + float color_rotation_angle = time * keplerian_speed * 0.3; vec3 rotated_color_pos = vec3( pos.x * cos(color_rotation_angle) - pos.z * sin(color_rotation_angle), pos.y, pos.x * sin(color_rotation_angle) + pos.z * cos(color_rotation_angle) ) * 1e-10; - + float large_color = fbm(rotated_color_pos * 1.8, 4); float medium_color = fbm(rotated_color_pos * 4.0, 3); float small_color = fbm(rotated_color_pos * 8.0, 2); float colorVariation = (large_color * 0.5 + medium_color * 0.3 + small_color * 0.2) * 0.6; baseColor = baseColor * (1.0 + colorVariation); - + float density = GetCloudDensity(pos); vec3 brightness_noise_pos = pos * 1e-10; - + float brightness_rotation_angle = time * keplerian_speed * 0.7; vec3 rotated_brightness_pos = vec3( pos.x * cos(brightness_rotation_angle) - pos.z * sin(brightness_rotation_angle), pos.y, pos.x * sin(brightness_rotation_angle) + pos.z * cos(brightness_rotation_angle) ) * 1e-10; - + float brightness_large = fbm(rotated_brightness_pos * 3.0, 3); float brightness_medium = fbm(rotated_brightness_pos * 5.0, 2); float brightness_small = fbm(rotated_brightness_pos * 7.0, 2); float brightness_noise = (brightness_large * 0.6 + brightness_medium * 0.3 + brightness_small * 0.1); - + // Enhanced brightness with glow effect float baseBrightness = 1.0 + density * 1.5; // Increased density contribution float glowBrightness = brightness_noise * 0.8; // Enhanced noise contribution float brightness = baseBrightness + glowBrightness; - + return vec4(baseColor * brightness, density); } -float CalculateAdaptiveStepSize(Ray ray, float baseStepSize) +float CalculateAdaptiveStepSize(Ray ray, float baseStepSize) { float r_factor = clamp(ray.r / (SagA_rs * 10.0), 0.1, 1.0); float curvature = length(vec3(ray.dr, ray.dtheta * ray.r, ray.dphi * ray.r * sin(ray.theta))); float curvature_factor = clamp(1e12 / (curvature + 1e6), 0.1, 2.0); - + return clamp(baseStepSize * r_factor * curvature_factor, MIN_STEP_SIZE, MAX_STEP_SIZE); } -void main() +void main() { - ivec2 pix = ivec2(gl_GlobalInvocationID.xy); - int WIDTH = imageSize(outImage).x; - int HEIGHT = imageSize(outImage).y; - - if (pix.x >= WIDTH || - pix.y >= HEIGHT) - return; + ivec2 pix = ivec2(gl_FragCoord.xy); + int WIDTH = int(u_Resolution.x); + int HEIGHT = int(u_Resolution.y); - float u = (2.0 * (pix.x + 0.5) / WIDTH - 1.0) * + float u = (2.0 * (pix.x + 0.5) / WIDTH - 1.0) * cam.aspect * cam.tanHalfFov; - float v = (1.0 - 2.0 * (pix.y + 0.5) / HEIGHT) * + float v = (1.0 - 2.0 * (pix.y + 0.5) / HEIGHT) * cam.tanHalfFov; - vec3 dir = normalize(u * cam.camRight - - v * cam.camUp + + vec3 dir = normalize(u * cam.camRight - + v * cam.camUp + cam.camForward); Ray ray = InitRay(cam.camPos, dir); @@ -372,15 +396,15 @@ void main() bool hitBlackHole = false; bool hitObject = false; - + vec4 accumulatedColor = vec4(0.0); float transmittance = 1.0; int maxSteps = cam.moving ? maxStepsMoving : maxStepsStatic; - + if (maxSteps <= 0) maxSteps = cam.moving ? DEFAULT_MAX_STEPS_MOVING : DEFAULT_MAX_STEPS_STATIC; - + float cameraDistance = length(cam.camPos); if (cameraDistance > 2e12) maxSteps = maxSteps / 2; @@ -388,83 +412,83 @@ void main() maxSteps = int(maxSteps * 0.75); float initialEscapeVelocity = sqrt(2.0 * SagA_rs / ray.r); - if (ray.dr > initialEscapeVelocity * 0.95 && + if (ray.dr > initialEscapeVelocity * 0.95 && ray.r > SagA_rs * 200.0) maxSteps = maxSteps / 2; float currentStepSize = D_LAMBDA; int objectCheckInterval = 5; - for (int i = 0; i < maxSteps; ++i) + for (int i = 0; i < maxSteps; ++i) { float exitDistance = earlyExitDistance > 0.0 ? earlyExitDistance : DEFAULT_EARLY_EXIT_DISTANCE; - if (ray.r > exitDistance) + if (ray.r > exitDistance) + break; + if (ray.r > ESCAPE_R) break; - if (ray.r > ESCAPE_R) + + if (Intercept(ray, SagA_rs)) + { + hitBlackHole = true; break; - - if (Intercept(ray, SagA_rs)) - { - hitBlackHole = true; - break; } - + currentStepSize = CalculateAdaptiveStepSize(ray, D_LAMBDA); - + RK4Step(ray, currentStepSize); lambda += currentStepSize; vec3 newPos = vec3(ray.x, ray.y, ray.z); - - if (IsInDiskVolume(newPos)) + + if (IsInDiskVolume(newPos)) { vec4 diskSample = SampleDiskColor(newPos); float density = diskSample.a; vec3 diskColor = diskSample.rgb; - + float stepLength = currentStepSize * 1e-8; - + float absorption = density * stepLength * 0.8; float scattering = density * stepLength * 1.5; float extinction = absorption + scattering; - + float stepTransmittance = exp(-extinction); - + vec3 emission = diskColor * density * stepLength * 4.0 * sqrt(disk_density); - + vec3 glowColor = mix(diskColor, vec3(1.0, 0.8, 0.6), 0.3); float glowIntensity = density * stepLength * 2.0; vec3 atmosphericGlow = glowColor * glowIntensity * 0.8; - + vec3 totalEmission = emission + atmosphericGlow; accumulatedColor.rgb += totalEmission * transmittance; - + transmittance *= stepTransmittance; - + if (transmittance < 0.01) { accumulatedColor.a = 1.0 - transmittance; break; } } - - if (i % objectCheckInterval == 0 && InterceptObject(ray)) - { - hitObject = true; - break; + + if (i % objectCheckInterval == 0 && InterceptObject(ray)) + { + hitObject = true; + break; } - + prevPos = newPos; - + if (ray.dr > 0.0 && ray.r > SagA_rs * 100.0 && lambda > 2e8) break; } accumulatedColor.a = 1.0 - transmittance; - + if (hitBlackHole) color = vec4(0.0, 0.0, 0.0, 1.0); - else if (hitObject) + else if (hitObject) { vec3 P = vec3(ray.x, ray.y, ray.z); vec3 N = normalize(P - hitCenter); @@ -484,5 +508,5 @@ void main() color = vec4(mix(accumulatedColor.rgb, hdriColor, 1.0 - accumulatedColor.a), 1.0); } - imageStore(outImage, pix, color); -}
\ No newline at end of file + fragColor = color; +} diff --git a/premake5.lua b/premake5.lua index 6225227..742c552 100644 --- a/premake5.lua +++ b/premake5.lua @@ -303,7 +303,8 @@ project "Donut" defines { - "_CRT_SECURE_NO_WARNINGS" + "_CRT_SECURE_NO_WARNINGS", + "GLFW_INCLUDE_NONE" } files @@ -351,14 +352,19 @@ project "Donut" filter "system:windows" systemversion "latest" - defines + + links { - "GLFW_INCLUDE_NONE" + "opengl32.lib", } - + + filter "system:macosx" links { - "opengl32.lib", + "Cocoa.framework", + "IOKit.framework", + "CoreFoundation.framework", + "QuartzCore.framework" } filter "configurations:Debug" diff --git a/src/Core/Window.cpp b/src/Core/Window.cpp index d0ca6d8..c890979 100644 --- a/src/Core/Window.cpp +++ b/src/Core/Window.cpp @@ -42,6 +42,17 @@ namespace Donut DONUT_INFO("GLFW initialized successfully"); } +#ifdef __APPLE__ + // macOS only exposes OpenGL up to 4.1 Core Profile, and requires a + // forward-compatible core-profile context for any modern (>= 3.3) + // shader to compile. Without these hints GLFW hands back a legacy + // 2.1 context and every GLSL shader in the project fails to build. + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); +#endif + m_Window = glfwCreateWindow(m_Width, m_Height, m_Title.c_str(), nullptr, nullptr); if (!m_Window) { @@ -118,7 +129,13 @@ namespace Donut ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; +#ifndef __APPLE__ + // Multi-viewport (dragging ImGui panels out as separate OS windows) + // relies on a populated platform-monitor list and is unreliable on + // macOS, where it intermittently asserts (Monitors.Size > 0) and + // aborts. Docking stays enabled; panels just remain inside the window. io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; +#endif SetupImGuiFonts(); ThemeManager::SetTheme(Theme::Dark); @@ -131,7 +148,14 @@ namespace Donut } ImGui_ImplGlfw_InitForOpenGL(m_Window, true); +#ifdef __APPLE__ + // macOS uses a core-profile context, which rejects the legacy + // "#version 130" GLSL the ImGui backend defaults to. 150 is the + // minimum core-profile GLSL that macOS's OpenGL 4.1 accepts. + ImGui_ImplOpenGL3_Init("#version 150"); +#else ImGui_ImplOpenGL3_Init("#version 130"); +#endif DONUT_INFO("ImGUI initialized successfully"); } diff --git a/src/Engine/Engine.cpp b/src/Engine/Engine.cpp index 453dce9..76be360 100644 --- a/src/Engine/Engine.cpp +++ b/src/Engine/Engine.cpp @@ -33,7 +33,10 @@ namespace Donut { glm::vec4(0.00f, 0.00f, 0.00f, m_SagA.m_Rs), glm::vec4(0, 0, 0, 1), static_cast<float>(m_SagA.m_Mass) } }; - m_ComputeProgram = CreateComputeProgram("Assets/Shaders/Geodesic.glsl"); + // The geodesic ray tracer used to be a compute shader; it is now a + // fullscreen vertex+fragment pass (see DispatchCompute) so it runs on + // macOS OpenGL 4.1, which has no compute shaders. + m_ComputeProgram = Ref<Shader>(Shader::Create("Assets/Shaders/Geodesic.glsl")); m_ShaderProgram = Ref<Shader>(Shader::Create("Assets/Shaders/TexturedQuad.glsl")); m_BlurShader = Ref<Shader>(Shader::Create("Assets/Shaders/Blur.glsl")); @@ -60,6 +63,26 @@ namespace Donut auto result = QuadVAO(); m_QuadVAO = result.first; m_Texture = result.second; + + // GLSL 4.10 forbids explicit binding qualifiers on uniform blocks, so + // associate the geodesic shader's blocks with their UBO binding points + // from the host side instead. + if (m_ComputeProgram) + { + uint32_t prog = m_ComputeProgram->GetRendererID(); + struct { const char* name; uint32_t point; } blocks[] = + { + { "Camera", 1 }, { "Disk", 2 }, { "Objects", 3 }, { "Simulation", 4 } + }; + for (const auto& b : blocks) + { + uint32_t idx = glGetUniformBlockIndex(prog, b.name); + if (idx != GL_INVALID_INDEX) + glUniformBlockBinding(prog, idx, b.point); + } + } + + glGenFramebuffers(1, &m_GeodesicFBO); } void Engine::UpdateWindowDimensions() @@ -126,30 +149,69 @@ namespace Donut RenderCommand::EnableDepthTest(); } + void Engine::DrawGeodesicPass(int cw, int ch) + { + m_QuadVAO->Bind(); + RenderCommand::DisableDepthTest(); + +#ifdef __APPLE__ + // macOS aborts any GPU submission that runs longer than a couple of + // seconds ("GPU Hang"). The geodesic ray-marcher can far exceed that in + // a single fullscreen draw, so render it in scissored tiles and flush + // after each, keeping every submission short enough to survive the + // watchdog. Compute-capable platforms draw it in one pass. + const int tile = 24; + glEnable(GL_SCISSOR_TEST); + for (int y = 0; y < ch; y += tile) + { + int th = std::min(tile, ch - y); + for (int x = 0; x < cw; x += tile) + { + int tw = std::min(tile, cw - x); + glScissor(x, y, tw, th); + RenderCommand::DrawArrays(6); + glFinish(); + } + } + glDisable(GL_SCISSOR_TEST); +#else + RenderCommand::DrawArrays(6); +#endif + + RenderCommand::EnableDepthTest(); + } + void Engine::DispatchCompute(const Camera& cam) { auto& hdriManager = HDRIManager::Get(); m_HDRIEnvironment = hdriManager.GetCurrentHDRI(); - + int cw = GetComputeWidth(); int ch = m_ComputeHeight; - m_Texture->SetData(nullptr, cw * ch * 4); + // Render the geodesic pass into m_Texture through an FBO. This replaces + // the old compute dispatch + imageStore path, which relied on OpenGL + // 4.3 compute and 4.2 image load/store that macOS does not provide. + glBindFramebuffer(GL_FRAMEBUFFER, m_GeodesicFBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_Texture->GetRendererID(), 0); + glViewport(0, 0, cw, ch); m_ComputeProgram->Bind(); UploadCameraUBO(cam); UploadDiskUBO(); UploadObjectsUBO(m_Objects); UploadSimulationUBO(); - m_Texture->BindAsImage(0, false); - + m_ComputeProgram->SetFloat2("u_Resolution", glm::vec2(static_cast<float>(cw), static_cast<float>(ch))); + if (m_HDRIEnvironment) + { m_HDRIEnvironment->Bind(5); - - uint32_t groupsX = static_cast<uint32_t>(std::ceil(cw / 16.0f)); - uint32_t groupsY = static_cast<uint32_t>(std::ceil(ch / 16.0f)); - m_ComputeProgram->Dispatch(groupsX, groupsY, 1); - m_ComputeProgram->MemoryBarrier(IMAGE_ACCESS_BARRIER_BIT); + m_ComputeProgram->SetInt("u_HDRIEnvironment", 5); + } + + DrawGeodesicPass(cw, ch); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); } void Engine::UploadCameraUBO(const Camera& cam) @@ -235,6 +297,15 @@ namespace Donut data.earlyExitDistance = m_EarlyExitDistance; data.time = static_cast<float>(glfwGetTime()) * m_RotationSpeed; +#ifdef __APPLE__ + // macOS has no compute shaders, so the geodesic pass runs as a tiled + // fragment shader under the OS GPU watchdog. The stock step counts + // (up to 30000) make a single tile exceed the watchdog and hang the + // GPU, so cap them here. Windows/Linux keep the full step count. + data.maxStepsMoving = std::min(data.maxStepsMoving, 10000); + data.maxStepsStatic = std::min(data.maxStepsStatic, 10000); +#endif + m_SimulationUBO->SetData(&data, sizeof(data)); m_SimulationUBO->Bind(4); } @@ -428,21 +499,20 @@ namespace Donut return; } - highResFramebuffer->Bind(); - - RenderCommand::SetViewport(0, 0, width, height); - RenderCommand::Clear(); - auto highResTexture = Texture2D::Create(computeWidth, computeHeight); if (!highResTexture) { DONUT_ERROR("Failed to create high-resolution texture"); return; } - - highResTexture->SetData(nullptr, computeWidth * computeHeight * 4); + + // Render the geodesic pass into highResTexture through the geodesic FBO. + glBindFramebuffer(GL_FRAMEBUFFER, m_GeodesicFBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, highResTexture->GetRendererID(), 0); + glViewport(0, 0, computeWidth, computeHeight); + m_ComputeProgram->Bind(); - + struct UBOData { glm::vec3 pos; float _pad0; @@ -470,17 +540,25 @@ namespace Donut m_CameraUBO->SetData(&data, sizeof(UBOData)); m_CameraUBO->Bind(1); - + UploadDiskUBO(); UploadObjectsUBO(m_Objects); UploadSimulationUBO(); - highResTexture->BindAsImage(0, false); - - uint32_t groupsX = static_cast<uint32_t>(std::ceil(computeWidth / 16.0f)); - uint32_t groupsY = static_cast<uint32_t>(std::ceil(computeHeight / 16.0f)); - m_ComputeProgram->Dispatch(groupsX, groupsY, 1); - m_ComputeProgram->MemoryBarrier(IMAGE_ACCESS_BARRIER_BIT); - + m_ComputeProgram->SetFloat2("u_Resolution", glm::vec2(static_cast<float>(computeWidth), static_cast<float>(computeHeight))); + + if (m_HDRIEnvironment) + { + m_HDRIEnvironment->Bind(5); + m_ComputeProgram->SetInt("u_HDRIEnvironment", 5); + } + + DrawGeodesicPass(computeWidth, computeHeight); + + // Display the rendered frame into the high-res framebuffer for read-back. + highResFramebuffer->Bind(); + RenderCommand::SetViewport(0, 0, width, height); + RenderCommand::Clear(); + m_ShaderProgram->Bind(); m_QuadVAO->Bind(); diff --git a/src/Engine/Engine.h b/src/Engine/Engine.h index c3cfdd0..f8ef628 100644 --- a/src/Engine/Engine.h +++ b/src/Engine/Engine.h @@ -89,7 +89,16 @@ namespace Donut void SetTargetFPS(int fps) { m_TargetFPS = fps; } int GetTargetFPS() const { return m_TargetFPS; } float GetCurrentFPS() const { return m_CurrentFPS; } - void SetComputeHeight(int height) { m_ComputeHeight = height; } + void SetComputeHeight(int height) + { +#ifdef __APPLE__ + // Without compute shaders the geodesic pass runs as a tiled + // fragment shader (see DrawGeodesicPass), so very high working + // resolutions make each frame take many seconds. Cap it on macOS. + if (height > 256) height = 256; +#endif + m_ComputeHeight = height; + } int GetComputeHeight() const { return m_ComputeHeight; } int GetComputeWidth() const { return (m_Width * m_ComputeHeight) / m_Height; } void UpdateComputeDimensions(); @@ -125,6 +134,11 @@ namespace Donut private: Ref<Shader> CreateComputeProgram(const char* path); std::pair<Ref<VertexArray>, Ref<Texture2D>> QuadVAO(); + + // Draws the bound geodesic shader over a cw x ch target. On macOS this + // is split into scissored tiles (with a flush each) so no single GPU + // submission trips the OS watchdog; elsewhere it is one fast draw. + void DrawGeodesicPass(int cw, int ch); private: Ref<VertexArray> m_QuadVAO; Ref<Texture2D> m_Texture; @@ -137,6 +151,11 @@ namespace Donut Ref<UniformBuffer> m_ObjectsUBO; Ref<UniformBuffer> m_SimulationUBO; + // FBO used to render the geodesic pass into m_Texture. The geodesic + // shader is a fragment shader (macOS has no compute), so it draws a + // fullscreen quad into this framebuffer instead of dispatching compute. + uint32_t m_GeodesicFBO = 0; + int m_Width; int m_Height; float m_Width_f = 100.0f*1e10f; diff --git a/src/Platform/OpenGL/OpenGLFramebuffer.cpp b/src/Platform/OpenGL/OpenGLFramebuffer.cpp index 3bb62f6..c46e0e6 100644 --- a/src/Platform/OpenGL/OpenGLFramebuffer.cpp +++ b/src/Platform/OpenGL/OpenGLFramebuffer.cpp @@ -19,7 +19,9 @@ namespace Donut static void CreateTextures(bool multisampled, uint32_t* outID, uint32_t count) { - glCreateTextures(TextureTarget(multisampled), count, outID); + // glCreateTextures is 4.5 DSA; macOS caps at 4.1. Callers bind each + // texture (with the correct target) before use. + glGenTextures(count, outID); } static void AttachColorTexture(uint32_t id, int samples, GLenum internalFormat, GLenum format, uint32_t width, uint32_t height, int index) @@ -31,7 +33,10 @@ namespace Donut } else { - glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr); + // Integer color formats require an integer pixel type even when + // data is null, or macOS's strict core profile rejects the call. + GLenum type = (format == GL_RED_INTEGER) ? GL_INT : GL_UNSIGNED_BYTE; + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -52,7 +57,10 @@ namespace Donut } else { - glTexStorage2D(GL_TEXTURE_2D, 1, format, width, height); + // glTexStorage2D is 4.2; use mutable storage for macOS (4.1). + GLenum depthFormat = (format == GL_DEPTH24_STENCIL8) ? GL_DEPTH_STENCIL : GL_DEPTH_COMPONENT; + GLenum depthType = (format == GL_DEPTH24_STENCIL8) ? GL_UNSIGNED_INT_24_8 : GL_FLOAT; + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, depthFormat, depthType, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -118,7 +126,7 @@ namespace Donut m_DepthAttachment = 0; } - glCreateFramebuffers(1, &m_RendererID); + glGenFramebuffers(1, &m_RendererID); // glCreateFramebuffers is 4.5 DSA; unavailable on macOS 4.1 glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID); bool multisample = m_Specification.Samples > 1; @@ -230,8 +238,9 @@ namespace Donut void OpenGLFramebuffer::ClearAttachment(uint32_t attachmentIndex, int value) { - auto& spec = m_ColorAttachmentSpecifications[attachmentIndex]; - glClearTexImage(m_ColorAttachments[attachmentIndex], 0, - Utils::DonutFBTextureFormatToGL(spec.TextureFormat), GL_INT, &value); + // glClearTexImage is 4.4 and unavailable on macOS. Clear the integer + // attachment by binding this framebuffer and clearing its draw buffer. + glBindFramebuffer(GL_FRAMEBUFFER, m_RendererID); + glClearBufferiv(GL_COLOR, static_cast<GLint>(attachmentIndex), &value); } }; diff --git a/src/Platform/OpenGL/OpenGLIndexBuffer.cpp b/src/Platform/OpenGL/OpenGLIndexBuffer.cpp index 7faee62..668a898 100644 --- a/src/Platform/OpenGL/OpenGLIndexBuffer.cpp +++ b/src/Platform/OpenGL/OpenGLIndexBuffer.cpp @@ -6,7 +6,7 @@ namespace Donut OpenGLIndexBuffer::OpenGLIndexBuffer(const uint32_t* indices, uint32_t count) : m_Count(count) { - glCreateBuffers(1, &m_RendererID); + glGenBuffers(1, &m_RendererID); // glCreateBuffers is 4.5 DSA; unavailable on macOS 4.1 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_RendererID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint32_t), indices, GL_STATIC_DRAW); } diff --git a/src/Platform/OpenGL/OpenGLRendererAPI.cpp b/src/Platform/OpenGL/OpenGLRendererAPI.cpp index 230b19b..fd3c491 100644 --- a/src/Platform/OpenGL/OpenGLRendererAPI.cpp +++ b/src/Platform/OpenGL/OpenGLRendererAPI.cpp @@ -103,7 +103,10 @@ namespace Donut void OpenGLRendererAPI::BindImageTexture(uint32_t textureID, uint32_t slot, bool readOnly) { - glBindImageTexture(slot, textureID, 0, GL_FALSE, 0, + // Image load/store is OpenGL 4.2; the pointer is null on macOS (4.1). + if (glBindImageTexture == nullptr) + return; + glBindImageTexture(slot, textureID, 0, GL_FALSE, 0, readOnly ? GL_READ_ONLY : GL_WRITE_ONLY, GL_RGBA8); } diff --git a/src/Platform/OpenGL/OpenGLShader.cpp b/src/Platform/OpenGL/OpenGLShader.cpp index b163cde..2992cdc 100644 --- a/src/Platform/OpenGL/OpenGLShader.cpp +++ b/src/Platform/OpenGL/OpenGLShader.cpp @@ -120,7 +120,15 @@ namespace Donut std::vector<char> infoLog(maxLength); glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]); glDeleteShader(shader); - std::cout << "Shader compilation failure!" << std::endl << infoLog.data() << std::endl; + for (auto id : glShaderIDs) + glDeleteShader(id); + glDeleteProgram(program); + m_RendererID = 0; + // infoLog.data() is null when the driver returns an empty log + // (e.g. macOS rejecting a compute shader); streaming a null + // char* into std::cout calls strlen(NULL) and crashes. + const char* log = infoLog.empty() ? "" : infoLog.data(); + std::cout << "Shader compilation failure!" << std::endl << log << std::endl; return; } glAttachShader(program, shader); @@ -141,7 +149,9 @@ namespace Donut glDeleteProgram(m_RendererID); for (auto id : glShaderIDs) glDeleteShader(id); - std::cout << "Shader link failure!" << std::endl << infoLog.data() << std::endl; + m_RendererID = 0; + const char* log = infoLog.empty() ? "" : infoLog.data(); + std::cout << "Shader link failure!" << std::endl << log << std::endl; return; } @@ -247,16 +257,25 @@ namespace Donut void OpenGLShader::Dispatch(uint32_t x, uint32_t y, uint32_t z) { + // Compute shaders require OpenGL 4.3+. On drivers that cap out earlier + // (e.g. macOS, which is frozen at 4.1) glDispatchCompute is never + // loaded and the pointer is null. Guard so we no-op instead of crash. + if (m_RendererID == 0 || glDispatchCompute == nullptr) + return; glDispatchCompute(x, y, z); } void OpenGLShader::DispatchIndirect(uint32_t offset) { + if (m_RendererID == 0 || glDispatchComputeIndirect == nullptr) + return; glDispatchComputeIndirect(offset); } void OpenGLShader::MemoryBarrier(uint32_t barriers) { + if (glMemoryBarrier == nullptr) + return; glMemoryBarrier(barriers); } }; diff --git a/src/Platform/OpenGL/OpenGLShader.h b/src/Platform/OpenGL/OpenGLShader.h index 99a150b..72d1d6e 100644 --- a/src/Platform/OpenGL/OpenGLShader.h +++ b/src/Platform/OpenGL/OpenGLShader.h @@ -48,7 +48,7 @@ namespace Donut std::unordered_map<uint32_t, std::string> PreProcess(const std::string& source); void Compile(const std::unordered_map<uint32_t, std::string>& shaderSources); private: - uint32_t m_RendererID; + uint32_t m_RendererID = 0; std::string m_Name; }; }; diff --git a/src/Platform/OpenGL/OpenGLTexture.cpp b/src/Platform/OpenGL/OpenGLTexture.cpp index ef5bdaa..1895575 100644 --- a/src/Platform/OpenGL/OpenGLTexture.cpp +++ b/src/Platform/OpenGL/OpenGLTexture.cpp @@ -8,6 +8,11 @@ #include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/type_ptr.hpp> +// NOTE: This file targets OpenGL 4.1 (the maximum macOS exposes). It uses the +// classic bind-based texture API rather than 4.5 Direct State Access +// (glCreateTextures / glTextureStorage2D / glTextureParameteri / glBindTextureUnit), +// none of which exist on macOS. + namespace Donut { OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height) @@ -16,14 +21,14 @@ namespace Donut m_InternalFormat = GL_RGBA8; m_DataFormat = GL_RGBA; - glCreateTextures(GL_TEXTURE_2D, 1, &m_RendererID); - glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height); - - glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glGenTextures(1, &m_RendererID); + glBindTexture(GL_TEXTURE_2D, m_RendererID); + glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, m_Width, m_Height, 0, m_DataFormat, GL_UNSIGNED_BYTE, nullptr); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); } OpenGLTexture2D::OpenGLTexture2D(const std::string& path) @@ -34,17 +39,17 @@ namespace Donut m_InternalFormat = GL_RGBA8; m_DataFormat = GL_RGBA; - glCreateTextures(GL_TEXTURE_2D, 1, &m_RendererID); - glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height); + glGenTextures(1, &m_RendererID); + glBindTexture(GL_TEXTURE_2D, m_RendererID); + glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, m_Width, m_Height, 0, m_DataFormat, GL_UNSIGNED_BYTE, nullptr); - glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); uint32_t whitePixel = 0xFFFFFFFF; - glTextureSubImage2D(m_RendererID, 0, 0, 0, m_Width, m_Height, m_DataFormat, GL_UNSIGNED_BYTE, &whitePixel); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_Width, m_Height, m_DataFormat, GL_UNSIGNED_BYTE, &whitePixel); DONUT_INFO("Created default texture (stb_image not available for loading: ", path, ")"); } @@ -62,17 +67,23 @@ namespace Donut DONUT_ERROR("Data must be entire texture!"); return; } - - glTextureSubImage2D(m_RendererID, 0, 0, 0, m_Width, m_Height, m_DataFormat, GL_UNSIGNED_BYTE, data); + + glBindTexture(GL_TEXTURE_2D, m_RendererID); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_Width, m_Height, m_DataFormat, GL_UNSIGNED_BYTE, data); } void OpenGLTexture2D::Bind(uint32_t slot) const { - glBindTextureUnit(slot, m_RendererID); + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_2D, m_RendererID); } void OpenGLTexture2D::BindAsImage(uint32_t slot, bool readOnly) const { + // Image load/store is OpenGL 4.2 and unavailable on macOS. Guard the + // function pointer so this degrades to a no-op instead of crashing. + if (glBindImageTexture == nullptr) + return; GLenum access = readOnly ? GL_READ_ONLY : GL_WRITE_ONLY; glBindImageTexture(slot, m_RendererID, 0, GL_FALSE, 0, access, m_InternalFormat); } @@ -80,17 +91,19 @@ namespace Donut OpenGLCubemapTexture::OpenGLCubemapTexture(uint32_t width, uint32_t height) : m_Width(width), m_Height(height) { - m_InternalFormat = GL_RGB16F; - m_DataFormat = GL_RGB; + m_InternalFormat = GL_RGBA16F; + m_DataFormat = GL_RGBA; - glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &m_RendererID); - glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height); + glGenTextures(1, &m_RendererID); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_RendererID); + for (uint32_t i = 0; i < 6; ++i) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, m_InternalFormat, m_Width, m_Height, 0, m_DataFormat, GL_FLOAT, nullptr); - glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); } OpenGLCubemapTexture::OpenGLCubemapTexture(const std::string& path) @@ -98,17 +111,19 @@ namespace Donut { m_Width = 1024; m_Height = 1024; - m_InternalFormat = GL_RGB16F; - m_DataFormat = GL_RGB; + m_InternalFormat = GL_RGBA16F; + m_DataFormat = GL_RGBA; - glCreateTextures(GL_TEXTURE_CUBE_MAP, 1, &m_RendererID); - glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height); + glGenTextures(1, &m_RendererID); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_RendererID); + for (uint32_t i = 0; i < 6; ++i) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, m_InternalFormat, m_Width, m_Height, 0, m_DataFormat, GL_FLOAT, nullptr); - glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTextureParameteri(m_RendererID, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); LoadHDRI(path); } @@ -123,11 +138,11 @@ namespace Donut stbi_set_flip_vertically_on_load(true); int width, height, channels; float* hdrData = stbi_loadf(path.c_str(), &width, &height, &channels, 3); - + if (!hdrData) { DONUT_ERROR("Failed to load HDRI: {}", path); - float defaultSky[6 * 4] = + float defaultSky[6 * 4] = { 0.5f, 0.7f, 1.0f, 1.0f, // Right 0.5f, 0.7f, 1.0f, 1.0f, // Left @@ -136,15 +151,16 @@ namespace Donut 0.5f, 0.7f, 1.0f, 1.0f, // Front 0.5f, 0.7f, 1.0f, 1.0f // Back }; - + + glBindTexture(GL_TEXTURE_CUBE_MAP, m_RendererID); for (int i = 0; i < 6; ++i) - glTextureSubImage3D(m_RendererID, 0, 0, 0, i, 1, 1, 1, GL_RGBA, GL_FLOAT, &defaultSky[i * 4]); + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, &defaultSky[i * 4]); return; } ConvertEquirectangularToCubemap(hdrData, width, height); stbi_image_free(hdrData); - + DONUT_INFO("Successfully loaded HDRI: {} ({}x{})", path, width, height); } @@ -174,10 +190,10 @@ namespace Donut DONUT_ERROR("Failed to create equirectangular to cubemap shader"); return; } - + uint32_t shaderProgram = equirectShader->GetRendererID(); - float vertices[] = + 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, -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, @@ -197,7 +213,7 @@ namespace Donut glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glm::mat4 captureProjection = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 10.0f); - glm::mat4 captureViews[] = + glm::mat4 captureViews[] = { glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::lookAt(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)), @@ -224,10 +240,10 @@ namespace Donut glDrawArrays(GL_TRIANGLES, 0, 36); } glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteVertexArrays(1, &cubeVAO); glDeleteBuffers(1, &cubeVBO); - glDeleteProgram(shaderProgram); glDeleteTextures(1, &hdrTexture); glDeleteFramebuffers(1, &captureFBO); glDeleteRenderbuffers(1, &captureRBO); @@ -240,12 +256,15 @@ namespace Donut void OpenGLCubemapTexture::Bind(uint32_t slot) const { - glBindTextureUnit(slot, m_RendererID); + glActiveTexture(GL_TEXTURE0 + slot); + glBindTexture(GL_TEXTURE_CUBE_MAP, m_RendererID); } void OpenGLCubemapTexture::BindAsImage(uint32_t slot, bool readOnly) const { + if (glBindImageTexture == nullptr) + return; GLenum access = readOnly ? GL_READ_ONLY : GL_WRITE_ONLY; glBindImageTexture(slot, m_RendererID, 0, GL_TRUE, 0, access, m_InternalFormat); } -};
\ No newline at end of file +}; diff --git a/src/Platform/OpenGL/OpenGLVertexArray.cpp b/src/Platform/OpenGL/OpenGLVertexArray.cpp index 6019287..afe5fe2 100644 --- a/src/Platform/OpenGL/OpenGLVertexArray.cpp +++ b/src/Platform/OpenGL/OpenGLVertexArray.cpp @@ -6,9 +6,11 @@ namespace Donut { - OpenGLVertexArray::OpenGLVertexArray() + OpenGLVertexArray::OpenGLVertexArray() { - glCreateVertexArrays(1, &m_RendererID); + // glCreateVertexArrays is 4.5 DSA; macOS caps at 4.1. glGenVertexArrays + // reserves the name and the VAO is created on first bind (done below). + glGenVertexArrays(1, &m_RendererID); } OpenGLVertexArray::~OpenGLVertexArray() diff --git a/src/Platform/OpenGL/OpenGLVertexBuffer.cpp b/src/Platform/OpenGL/OpenGLVertexBuffer.cpp index 21f152c..4f5e48f 100644 --- a/src/Platform/OpenGL/OpenGLVertexBuffer.cpp +++ b/src/Platform/OpenGL/OpenGLVertexBuffer.cpp @@ -7,7 +7,7 @@ namespace Donut { OpenGLVertexBuffer::OpenGLVertexBuffer(const void* data, uint32_t size) { - glCreateBuffers(1, &m_RendererID); + glGenBuffers(1, &m_RendererID); // glCreateBuffers is 4.5 DSA; unavailable on macOS 4.1 glBindBuffer(GL_ARRAY_BUFFER, m_RendererID); glBufferData(GL_ARRAY_BUFFER, size, data, GL_STATIC_DRAW); } |
