aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-18 15:00:34 +0200
committerhachem <im@hachem.wtf>2026-08-18 15:00:34 +0200
commitdfe9da43dab6c933bc1024f992383f08e01386de (patch)
tree8f52e80e0b2a65eaacdbf5fa056721e7fa4e9fa5
parent86df8b19063f73b7803daaa1e5b21da33ac976ed (diff)
[fix]: macos compatibility
-rw-r--r--Assets/Shaders/Geodesic.glsl258
-rw-r--r--premake5.lua16
-rw-r--r--src/Core/Window.cpp24
-rw-r--r--src/Engine/Engine.cpp130
-rw-r--r--src/Engine/Engine.h21
-rw-r--r--src/Platform/OpenGL/OpenGLFramebuffer.cpp23
-rw-r--r--src/Platform/OpenGL/OpenGLIndexBuffer.cpp2
-rw-r--r--src/Platform/OpenGL/OpenGLRendererAPI.cpp5
-rw-r--r--src/Platform/OpenGL/OpenGLShader.cpp23
-rw-r--r--src/Platform/OpenGL/OpenGLShader.h2
-rw-r--r--src/Platform/OpenGL/OpenGLTexture.cpp113
-rw-r--r--src/Platform/OpenGL/OpenGLVertexArray.cpp6
-rw-r--r--src/Platform/OpenGL/OpenGLVertexBuffer.cpp2
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);
}