#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GLM_FORCE_PURE #include "colormap_png.h" #include "sates.h" #include "sk_png.h" #include "stb_image.h" #include "vec23.h" #include #include #include #include #include #include #include #include #include #ifndef ENABLE_NXLINK #define TRACE(fmt, ...) ((void)0) #else #include #define TRACE(fmt, ...) printf("%s: " fmt "\n", __PRETTY_FUNCTION__, ##__VA_ARGS__) static int s_nxlinkSock = -1; static void initNxLink() { if (R_FAILED(socketInitializeDefault())) return; s_nxlinkSock = nxlinkStdio(); if (s_nxlinkSock >= 0) TRACE("printf output now goes to nxlink server"); else socketExit(); } static void deinitNxLink() { if (s_nxlinkSock >= 0) { close(s_nxlinkSock); socketExit(); s_nxlinkSock = -1; } } extern "C" void userAppInit() { initNxLink(); } extern "C" void userAppExit() { deinitNxLink(); } #endif static EGLDisplay s_display; static EGLContext s_context; static EGLSurface s_surface; static bool initEgl(NWindow *win) { s_display = eglGetDisplay(EGL_DEFAULT_DISPLAY); if (!s_display) { TRACE("Could not connect to display! error: %d", eglGetError()); goto _fail0; } eglInitialize(s_display, nullptr, nullptr); if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { TRACE("Could not set API! error: %d", eglGetError()); goto _fail1; } EGLConfig config; EGLint numConfigs; static const EGLint framebufferAttributeList[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, EGL_NONE }; eglChooseConfig(s_display, framebufferAttributeList, &config, 1, &numConfigs); if (numConfigs == 0) { TRACE("No config found! error: %d", eglGetError()); goto _fail1; } s_surface = eglCreateWindowSurface(s_display, config, win, nullptr); if (!s_surface) { TRACE("Surface creation failed! error: %d", eglGetError()); goto _fail1; } static const EGLint contextAttributeList[] = { EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, EGL_CONTEXT_MAJOR_VERSION_KHR, 4, EGL_CONTEXT_MINOR_VERSION_KHR, 3, EGL_NONE }; s_context = eglCreateContext(s_display, config, EGL_NO_CONTEXT, contextAttributeList); if (!s_context) { TRACE("Context creation failed! error: %d", eglGetError()); goto _fail2; } eglMakeCurrent(s_display, s_surface, s_surface, s_context); return true; _fail2: eglDestroySurface(s_display, s_surface); s_surface = nullptr; _fail1: eglTerminate(s_display); s_display = nullptr; _fail0: return false; } static void deinitEgl() { if (s_display) { eglMakeCurrent(s_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); if (s_context) { eglDestroyContext(s_display, s_context); s_context = nullptr; } if (s_surface) { eglDestroySurface(s_display, s_surface); s_surface = nullptr; } eglTerminate(s_display); s_display = nullptr; } } static const char *const text_vs = R"text( #version 330 core layout(location=0) in vec2 inPos; layout(location=1) in vec3 inColor; out vec3 color; void main() { color = inColor; gl_Position = vec4(inPos, 0.0, 1.0); } )text"; static const char *const text_fs = R"text( #version 330 core in vec3 color; out vec4 fragColor; void main() { fragColor = vec4(color, 1.0); } )text"; static GLuint s_textProgram = 0; static GLuint s_textVao = 0; static GLuint s_textVbo = 0; static float *s_textVertexData = nullptr; static const int TEXT_BUFFER_FLOATS = 100 * 64 * 6 * 5; static const unsigned char font8x8[11][8] = { { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 }, { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 }, { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 }, { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 }, { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 }, { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 }, { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 }, { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 }, { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 }, { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 }, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 } }; static GLuint createAndCompileShader(GLenum type, const char *source); static void initTextRenderer() { GLuint vsh = createAndCompileShader(GL_VERTEX_SHADER, text_vs); GLuint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, text_fs); s_textProgram = glCreateProgram(); glAttachShader(s_textProgram, vsh); glAttachShader(s_textProgram, fsh); glLinkProgram(s_textProgram); glDeleteShader(vsh); glDeleteShader(fsh); glGenVertexArrays(1, &s_textVao); glGenBuffers(1, &s_textVbo); s_textVertexData = (float *)aligned_alloc(64, TEXT_BUFFER_FLOATS * sizeof(float)); } static void drawTextPixel(float x, float y, float size, float r, float g, float b, float *vertexData, int *offset) { float verts[] = { x, y, r, g, b, x + size, y, r, g, b, x + size, y + size, r, g, b, x, y, r, g, b, x + size, y + size, r, g, b, x, y + size, r, g, b }; memcpy(&vertexData[*offset], verts, sizeof(verts)); *offset += 30; } static void drawChar(char c, float x, float y, float scale, float r, float g, float b, float *vertexData, int *offset) { int idx = -1; if (c >= '0' && c <= '9') idx = c - '0'; else if (c == '.') idx = 10; else return; const unsigned char *glyph = font8x8[idx]; for (int row = 0; row < 8; row++) { for (int col = 0; col < 8; col++) { if (glyph[row] & (1 << col)) { float px = x + col * scale; float py = y - row * scale; drawTextPixel(px, py, scale, r, g, b, vertexData, offset); } } } } static void drawText(const char *text, float x, float y, float scale, float r, float g, float b) { float *vertexData = s_textVertexData; int offset = 0; float cx = x; while (*text) { drawChar(*text, cx, y, scale, r, g, b, vertexData, &offset); cx += 8 * scale; text++; } if (offset > 0) { glUseProgram(s_textProgram); glBindVertexArray(s_textVao); glBindBuffer(GL_ARRAY_BUFFER, s_textVbo); glBufferData(GL_ARRAY_BUFFER, offset * sizeof(float), vertexData, GL_DYNAMIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(2 * sizeof(float))); glEnableVertexAttribArray(1); glDrawArrays(GL_TRIANGLES, 0, offset / 5); } } static void cleanupTextRenderer() { if (s_textVbo) { glDeleteBuffers(1, &s_textVbo); s_textVbo = 0; } if (s_textVao) { glDeleteVertexArrays(1, &s_textVao); s_textVao = 0; } if (s_textProgram) { glDeleteProgram(s_textProgram); s_textProgram = 0; } if (s_textVertexData) { free(s_textVertexData); s_textVertexData = nullptr; } } static void setMesaConfig() { setenv("MESA_NO_ERROR", "1", 1); setenv("EGL_LOG_LEVEL", "debug", 1); setenv("MESA_VERBOSE", "all", 1); } static u64 s_startTicks = 0; static u64 s_lastFrameTime = 0; static float s_fps = 0.0f; static int s_frameCount = 0; static u64 s_fpsUpdateTime = 0; // static int frame = 0; static const char *const rt_vs = R"text( #version 330 core out vec2 uv; void main() { vec2 pos = vec2( (gl_VertexID == 2) ? 3.0 : -1.0, (gl_VertexID == 1) ? 3.0 : -1.0 ); uv = 0.5 * (pos + 1.0); gl_Position = vec4(pos, 0.0, 1.0); } )text"; static const char *const rt_fs = R"text( #version 330 core in vec2 uv; out vec4 fragColor; uniform sampler2D screenTex; void main(){ fragColor = texture(screenTex, uv); } )text"; static GLuint s_program; static GLuint s_vao, s_vbo; // static GLint resolutionLoc; // static GLint loc_mdlvMtx, loc_projMtx; static GLint loc_time; static GLuint createAndCompileShader(GLenum type, const char *source) { GLint success; GLchar msg[4096]; GLuint handle = glCreateShader(type); if (!handle) { TRACE("%u: cannot create shader", type); return 0; } glShaderSource(handle, 1, &source, nullptr); glCompileShader(handle); glGetShaderiv(handle, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(handle, sizeof(msg), nullptr, msg); TRACE("%u: %s\n", type, msg); glDeleteShader(handle); return 0; } return handle; } static int RenderX = 1280; static int RenderY = 720; // static int OutputX = 1280; // static int OutputY = 720; static const int THREAD_COUNT = 2; static std::thread workers[THREAD_COUNT]; alignas(64) static std::atomic running(true); static std::mutex workMutex; static std::condition_variable workCV; // alignas(64) static bool workReady = false; // alignas(64) static int currentFrame = 0; static void pinThread(int core) { Handle thread = CUR_THREAD_HANDLE; svcSetThreadCoreMask(thread, core, 1ULL << core); } static const char *const vertexShaderSource = R"text( #version 330 core layout(location = 0) in vec3 position; out vec2 uv; void main() { uv = (position.xy + 1.0) * 0.5; gl_Position = vec4(position, 1.0); } )text"; static const char *const fragmentShaderSource = R"text( #version 330 core // Constants const float PI = 3.14159265359; const float EPSILON = 0.0001; const float INFINITY = 1000000.0; out vec4 fragColor; uniform vec2 res; uniform float time; // textures uniform samplerCube galaxy; uniform sampler2D colorMap; // Rendering params uniform float frontView = 0.0; uniform float topView = 0.0; uniform float cameraRoll = 0.0; // checkerboard uniform int frameIndex; uniform sampler3D noiseTex; // Precomp radial bands uniform sampler2D orbitalLUT; // h2 Interval // uniform int h2Interval; // Phys params uniform float fovScale = 1.0; // Accretion disk uniform float adiskHeight = 0.1; uniform float adiskLit = 15; uniform float adiskDensityV = 2.0; uniform float adiskDensityH = 1.5; uniform float adiskNoiseScale = 0.4; uniform float adiskNoiseLOD = 5.0; uniform float adiskSpeed = 0.12; uniform float orbitalScale; uniform sampler2D deflectionMap; uniform float photonScreenRadius; uniform sampler2D diskColorMap; struct Ring { vec3 center; vec3 normal; float innerRadius; float outerRadius; float rotateSpeed; }; // Simplex 3D Noise // by Ian McEwan, Ashima Arts vec4 permute(vec4 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); } vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; } float snoise(vec3 v) { const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); // 1st corner vec3 i = floor(v + dot(v, C.yyy)); vec3 x0 = v - i + dot(i, C.xxx); // Other corners vec3 g = step(x0.yzx, x0.xyz); vec3 l = 1.0 - g; vec3 i1 = min(g.xyz, l.zxy); vec3 i2 = max(g.xyz, l.zxy); // x0 = x0 - 0. + 0.0 * C vec3 x1 = x0 - i1 + 1.0 * C.xxx; vec3 x2 = x0 - i2 + 2.0 * C.xxx; vec3 x3 = x0 - 1. + 3.0 * C.xxx; // Permutations i = mod(i, 289.0); vec4 p = permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + vec4(0.0, i1.x, i2.x, 1.0)); // Gradients // N * N points uniformly over a square, mapped to an octahedron float n_ = 1.0 / 7.0; // N=7 vec3 ns = n_ * D.wyz - D.xzx; vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,N*N) vec4 x_ = floor(j * ns.z); vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) vec4 x = x_ * ns.x + ns.yyyy; vec4 y = y_ * ns.x + ns.yyyy; vec4 h = 1.0 - abs(x) - abs(y); vec4 b0 = vec4(x.xy, y.xy); vec4 b1 = vec4(x.zw, y.zw); vec4 s0 = floor(b0) * 2.0 + 1.0; vec4 s1 = floor(b1) * 2.0 + 1.0; vec4 sh = -step(h, vec4(0.0)); vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; vec3 p0 = vec3(a0.xy, h.x); vec3 p1 = vec3(a0.zw, h.y); vec3 p2 = vec3(a1.xy, h.z); vec3 p3 = vec3(a1.zw, h.w); // Normalize Gradients vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w; // Mix final noise val vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0); m = m * m; return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); } // Ring computes float ringDistance(vec3 rayOrigin, vec3 rayDir, Ring ring){ float denominator = dot(rayDir, ring.normal); float constant = -dot(ring.center, ring.normal); if (abs(denominator) < EPSILON) { return -1.0; } else { float t = -(dot(rayOrigin, ring.normal) + constant) / denominator; if (t < 0.0) { return -1.0; } vec3 intersection = rayOrigin + t * rayDir; // Comp distance to ring center float d = length(intersection - ring.center); if (d >= ring.innerRadius && d <= ring.outerRadius) { return t; } return -1.0; } } vec3 panoramaColor (sampler2D tex, vec3 dir) { vec2 uv = vec2(0.5 - atan(dir.z, dir.x) / PI * 0.5, 0.5 - asin(dir.y) / PI); return texture2D(tex, uv).rgb; } vec3 accel(float h2, vec3 pos) { float r2 = dot(pos, pos); float r4 = r2 * r2; float r2_5 = r4 * sqrt(r2); vec3 acc = -1.5 * h2 * pos / r2_5; return acc; } vec4 quadFromAxisAngle(vec3 axis, float angle) { vec4 qr; float half_angle = (angle * 0.5) * 3.14159 / 180.0; qr.x = axis.x * sin(half_angle); qr.y = axis.y * sin(half_angle); qr.z = axis.z * sin(half_angle); qr.w = cos(half_angle); return qr; } vec4 quadConj(vec4 q) { return vec4(-q.x, -q.y, -q.z, q.w); } vec4 quat_mult(vec4 q1, vec4 q2) { vec4 qr; qr.x = (q1.w * q2.x) + (q1.x * q2.w) + (q1.y * q2.z) - (q1.z * q2.y); qr.y = (q1.w * q2.y) - (q1.x * q2.z) + (q1.y * q2.w) + (q1.z * q2.x); qr.z = (q1.w * q2.z) + (q1.x * q2.y) - (q1.y * q2.x) + (q1.z * q2.w); qr.w = (q1.w * q2.w) - (q1.x * q2.x) - (q1.y * q2.y) - (q1.z * q2.z); return qr; } vec3 rotateVector(vec3 position, vec3 axis, float angle) { vec4 qr = quadFromAxisAngle(axis, angle); vec4 qr_conj = quadConj(qr); vec4 q_pos = vec4(position.x, position.y, position.z, 0); vec4 q_tmp = quat_mult(qr, q_pos); qr = quat_mult(q_tmp, qr_conj); return vec3(qr.x, qr.y, qr.z); } #define IN_RANGE(x, a, b) (((x) > (a)) && ((x) < (b))) void cartesianToSpherical(in vec3 xyz, out float rho, out float phi, out float theta) { rho = sqrt((xyz.x * xyz.x) + (xyz.y * xyz.y) + (xyz.z * xyz.z)); phi = asin(xyz.y / rho); theta = atan(xyz.z, xyz.x); } // Cartesian -> Spherical coord vec3 toSpherical(vec3 p) { float rho = sqrt((p.x * p.x) + (p.y * p.y) + (p.z * p.z)); float theta = atan(p.z, p.x); float phi = asin(p.y / rho); return vec3(rho, theta, phi); } vec3 toSpherical2(vec3 pos) { vec3 radialCoords; radialCoords.x = length(pos) * 1.5 + 0.55; radialCoords.y = atan(-pos.x, -pos.z) * 1.5; radialCoords.z = abs(pos.y); return radialCoords; } // ring color void ringColor(vec3 rayOrigin, vec3 rayDir, Ring ring, inout float minDistance, inout vec3 color) { float distance = ringDistance(rayOrigin, normalize(rayDir), ring); if (distance >= EPSILON && distance < minDistance && distance <= length(rayDir) + EPSILON) { minDistance = distance; vec3 intersection = rayOrigin + normalize(rayDir) * minDistance; vec3 ringColor; { float dist = length(intersection); float v = clamp((dist - ring.innerRadius) / (ring.outerRadius - ring.innerRadius), 0.0, 1.0); vec3 base = cross(ring.normal, vec3(0.0, 0.0, 1.0)); float angle = acos(dot(normalize(base), normalize(intersection))); if (dot(cross(base, intersection), ring.normal) < 0.0) angle = -angle; float u = 0.5 - 0.5 * angle / PI; //.Hack u += time * ring.rotateSpeed; vec3 color = vec3(0.0, 0.5, 0.0); //.Hack GU float alpha = 0.5; ringColor = vec3(color); } color += ringColor; } } mat3 lookAt(vec3 origin, vec3 target, float roll){ vec3 rr = vec3(sin(roll), cos(roll), 0.0); vec3 ww = normalize(target - origin); vec3 uu = normalize(cross(ww, rr)); vec3 vv = normalize(cross(uu, ww)); return mat3(uu, vv, ww); } float sqrLength(vec3 a) { return dot(a, a); } // Accretion Disc // for laughs, I tried running this bullshit on my PC with a 5060, at 1080p this thing was dying // I am terrified at how its going to run on NX void adiskColor(vec3 pos, inout vec3 color, inout float alpha, float stepSize, vec3 dir) { float innerRadius = 2.6; float outerRadius = 12.0; //Accertion disks increase in density the closer we get to the event horizon float thinDiskHeight = 0.18; float density = max(0.0, 1.0 - length(pos.xyz / vec3(outerRadius, thinDiskHeight, outerRadius))); if (density < 0.001) { return; } // Set particles to 0 once we go past the innermost circular orbit density *= pow(1.0 - abs(pos.y) / thinDiskHeight, 6.0); density *= smoothstep(innerRadius, innerRadius + 0.4, length(pos.xz)); // Dont compute if the density is tiny if (density < 0.003) { return; } float r = length(pos.xz); // hell float theta = atan(pos.z, pos.x); float radialU = clamp((r - innerRadius) / (outerRadius - innerRadius), 0.0, 1.0); vec2 cs = textureLod(orbitalLUT, vec2(radialU, 0.5), 0.0).rg; float cosT = cos(theta); float sinT = sin(theta); float cosA = cosT * cs.x - sinT * cs.y; float sinA = sinT * cs.x + cosT * cs.y; // Rotate the actual local XZ space smoothly over time vec3 rotatedPos = pos; rotatedPos.x = pos.x * cosA - pos.z * sinA; rotatedPos.z = pos.x * sinA + pos.z * cosA; // Noise texture parameters // noise size, higher = finer grain float noise = 0.0; float amp = 1.0; float scale = 0.35; float totalAmp = 0.0; // Stretch factors across respective axis float stretchX = 1.0; float stretchY = 2.0; float stretchZ = 0.2; // Frequency mul per octave, higher = finer detail float lacunarity = 1.8; // Brightness drop per octave, higher = sharper micro grain float gain = 0.45; #define NOISE_LOD 4 for (int i = 0; i < NOISE_LOD; i++) { // Use positions along flow direction to map correctly vec3 sampleCoords = vec3(rotatedPos.x * stretchX, rotatedPos.y * stretchY, rotatedPos.z * stretchZ); float n = textureLod(noiseTex, sampleCoords, 0.0).r * 2.0 - 1.0; noise += n * amp; totalAmp += amp; scale *= lacunarity; amp *= gain; } #undef NOISE_LOD // Normalize to [0,1] noise = clamp(noise / totalAmp * 0.5 + 0.5, 0.0, 1.0); // Add dopler shifiting // Basically, in a black hole the accretion disk is spinning so rapidly that it significantly changes light // The side approaching the obvserver will be blueshifted, while the opposing side will be redshifted // Disc rotation vec3 orbitalVelDir = normalize(vec3(-pos.z, 0.0, pos.x)); // Vel approximation float speed = clamp(0.65 / sqrt(max(r, 2.0)), 0.0, 0.6); vec3 velocityVector = orbitalVelDir * speed; // Dot product between orbital velocity and C towards camera float cosTheta = dot(velocityVector, -normalize(dir)); // Lorentz factor (time dialation) // Stay with me, as we cross the empty skies float gamma = 1.0 / sqrt(1.0 - speed * speed); // Come sail with me float doppler = 1.0 / (gamma * (1.0 - cosTheta * speed)); // Time, Shift // We discover the entry // To other planes // accretion disk colors, based on their distance // Apply whitening with blue shift vec3 innerColor = vec3(1.3, 1.1, 0.9) * pow(doppler, 1.5); vec3 midColor = vec3(1.0, 0.4, 0.05); vec3 outerColor = vec3(0.4, 0.02, 0.0); // Stay with me // Come sail with me float radialT = clamp((r - innerRadius) / (outerRadius - innerRadius), 0.0, 1.0); vec3 dustColor = (radialT < 0.2) ? mix(innerColor, midColor, radialT / 0.2) : mix(midColor, outerColor, (radialT - 0.2) / 0.8); // We fly in dreams // As we cross the space and time // Just stay with me // Scaling factor for relativistic beaming (scales @3.5/4^) float beaming = pow(doppler, 4.0); // Scale colors and attenuate alpha for volumetric depth // Controls disk opacity float absorption = 1.2; float sampleAlpha = clamp(density * noise * stepSize * absorption * beaming, 0.0, 1.0); color += dustColor * adiskLit * sampleAlpha * alpha * beaming; // Attenuate alpha for realistic occlusion alpha *= (1.0 - sampleAlpha); } float hash2d(vec2 co) { return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); } // Nebula/Skybox gen // Reads from the actual galaxy texture first, then creates stars with varying intensities float _starHash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } vec3 sampleSky(vec3 dir){ vec3 sky = textureLod(galaxy, dir, 0.0).rgb; float phi = atan(dir.z, dir.x) * (1.0 / (2.0 * PI)) + 0.5; float theta = asin(clamp(dir.y, -1.0, 1.0)) * (1.0 / PI) + 0.5; vec2 uv = vec2(phi, theta); // Star field vec2 grid = uv * vec2(600.0, 300.0); vec2 cell = floor(grid); vec2 local = fract(grid); float brightness = 0.0; for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { vec2 nc = cell + vec2(float(dx), float(dy)); // Random sub-cell position and magnitude float jx = _starHash(nc); float jy = _starHash(nc + vec2(1.3, 7.4)); float mag = _starHash(nc + vec2(3.7, 2.1)); // Power law for formula, mostly dim stars with rare bight points mag = pow(mag, 5.0); float d = length(local - vec2(float(dx), float(dy)) - vec2(jx, jy)); brightness += smoothstep(0.045, 0.0, d) * mag; } } // Add star color, some closer to A/O types while others are K/G types float warmth = _starHash(cell + vec2(9.1, 4.6)); vec3 starCol = mix(vec3(0.82, 0.91, 1.0), vec3(1.0, 0.82, 0.55), step(0.85, warmth)); // XMB ass wave float band = exp(-abs(dir.y) * 5.5) * 0.022; vec3 nebula = vec3(0.12, 0.18, 0.38) * band; return sky + starCol * brightness + nebula; } vec3 traceColor(vec3 pos, vec3 dir) { vec3 color = vec3(0.0); float alpha = 1.0; dir = normalize(dir); //Initial val vec3 h = cross(pos, dir); float h2 = dot(h, h); // Dithering float dither = hash2d(vec2(gl_FragCoord.xy)); float initialStep = clamp(length(pos) * 0.04, 0.02, 0.3); pos += dir * (initialStep * dither); // Ray iterations count for (int i = 0; i < 320; i++) { // Dynamically change steps taken based on distance from the black hole float dist = length(pos); float stepSize = clamp(dist * 0.04, 0.02, 0.3); // scale lensing by step size // Original didn't, and while it worked, dynamic changes break it vec3 acc = accel(h2, pos) * stepSize; dir += acc; h = cross(pos, dir); h2 = dot(h, h); if (dot(pos, pos) < 1.0) return color; // pass dynamic step sizes adiskColor(pos, color, alpha, stepSize, dir); pos += dir * stepSize; } dir = rotateVector(dir, vec3(0.0, 1.0, 0.0), time); color += sampleSky(dir) * alpha; return color; } // COUGH COUGH WHAT THE FUCK AHH uniform vec3 camPos; uniform mat3 view; void main() { ivec2 coord = ivec2(gl_FragCoord.xy); if ((coord.x + coord.y + frameIndex) % 2 != 0){ discard; } vec2 uv = gl_FragCoord.xy / res - vec2(0.5); uv.x *= res.x / res.y; // Distance from black hole, good enough for LOD float screenDist = length(uv); vec3 dir = view * normalize(vec3(-uv.x * fovScale, uv.y * fovScale, 1.0)); vec3 color; if (screenDist > photonScreenRadius) { // If we can, use the fast path // Sample the CPU-computed exit direction vec2 deflectUV = gl_FragCoord.xy / res; // [0,1] vec4 deflection = textureLod(deflectionMap, deflectUV, 0.0); if (deflection.w < 0.5) { // if hit BH, black color = vec3(0.0); } else { // Exit direction from CPU vec3 exitDir = normalize(deflection.xyz); vec4 cpuDisk = textureLod(diskColorMap, deflectUV, 0.0); color = cpuDisk.rgb; float alpha = cpuDisk.a; // Apply time to match GPU exitDir = rotateVector(exitDir, vec3(0.0, 1.0, 0.0), time); color += sampleSky(exitDir) * alpha; } } else { // If we are close enough, use the full tracing color = traceColor(camPos, dir); } fragColor = vec4(color, 1.0); } )text"; static const char *const bloom_extract_fs = R"text( #version 330 core in vec2 uv; out vec4 fragColor; uniform sampler2D sceneTex; uniform float threshold; void main() { vec3 color = texture(sceneTex, uv).rgb; // Luminance val float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722)); // Softer fall off instead of hard edges float contrib = smoothstep(threshold, threshold + 0.3, brightness); fragColor = vec4(color * contrib, 1.0); } )text"; static const char *const bloom_blur_fs = R"text( #version 330 core in vec2 uv; out vec4 fragColor; uniform sampler2D blurTex; uniform vec2 direction; // (1,0) or (0,1) uniform vec2 texelSize; // 1.0 / vec2(BLOOM_W, BLOOM_H) // 9-tap Gaussian weights const float weight[5] = float[](0.227027, 0.194595, 0.121622, 0.054054, 0.016216); void main() { vec3 result = texture(blurTex, uv).rgb * weight[0]; vec2 step = direction * texelSize; for (int i = 1; i < 5; i++) { result += texture(blurTex, uv + step * float(i)).rgb * weight[i]; result += texture(blurTex, uv - step * float(i)).rgb * weight[i]; } fragColor = vec4(result, 1.0); } )text"; static const char *const bloom_composite_fs = R"text( #version 330 core in vec2 uv; out vec4 fragColor; uniform sampler2D sceneTex; uniform sampler2D bloomTex; uniform float bloomStrength; void main() { vec3 scene = texture(sceneTex, uv).rgb; vec3 bloom = texture(bloomTex, uv).rgb; //Make sure that bloom never darkens vec3 result = scene + bloom * bloomStrength; // Make sure that the bloom doesn't snap to white result = vec3(1.0) - exp(-result * 1.2); fragColor = vec4(result, 1.0); } )text"; static const char *const checkerboard_resolve_fs = R"text( #version 330 core in vec2 uv; out vec4 fragColor; uniform sampler2D currentTex; uniform sampler2D previousTex; uniform int frameIndex; uniform vec2 texelSize; void main() { ivec2 coord = ivec2(gl_FragCoord.xy); bool thisFramePixel = ((coord.x + coord.y + frameIndex) % 2 == 0); if (thisFramePixel) { // ie, if pixel X is written, use it directly fragColor = texture(currentTex, uv); } else { // Reconstruct from the previous frame with a 4 average for smoothing // Ie, if this pixel was skipped use data from 4 average vec3 c = texture(previousTex, uv).rgb; c += texture(previousTex, uv + vec2( texelSize.x, 0.0)).rgb; c += texture(previousTex, uv + vec2(-texelSize.x, 0.0)).rgb; c += texture(previousTex, uv + vec2(0.0, texelSize.y)).rgb; fragColor = vec4(c * 0.25, 1.0); } } )text"; // static GLint res; static GLuint tex1; static GLuint tex2; static GLint resloc; static GLint loc_camPos; static GLint loc_view; static GLuint s_bloomExtractProg; static GLuint s_bloomBlurProg; static GLuint s_bloomCompositeProg; static GLuint s_sceneFbo, s_sceneTex; static GLuint s_bloomFboA, s_bloomTexA; static GLuint s_bloomFboB, s_bloomTexB; static GLint s_blur_dirLoc; static GLint s_blur_texelLoc; static GLint s_composite_bloomStrengthLoc; static const int BLOOM_W = 320; static const int BLOOM_H = 180; static GLuint s_prevSceneFbo; static GLuint s_prevSceneTex; static GLuint s_resolveProg; static GLint s_resolve_frameIndexLoc; // static GLint s_resolve_texelSizeLoc; static GLint s_rt_frameIndexLoc; static int s_frameIndex = 0; static GLuint s_noiseTex3D; static GLint s_noiseTexLoc; static const int DEFLECT_W = 480; static const int DEFLECT_H = 270; static GLuint s_deflectionTex; static GLint s_deflectionTexLoc; static GLuint s_diskColorTex; static GLint s_diskColorTexLoc; alignas(64) static float s_diskColorBuf[2][DEFLECT_W * DEFLECT_H * 4]; static std::thread s_deflectThreads[2]; static std::atomic s_targetFrame{ 0 }; static std::atomic s_threadsFinished{ 0 }; static std::chrono::time_point s_frameStartTime; static std::atomic s_currentCpuFps{ 0.0f }; static std::atomic s_lastCpuLatencyMs{ 0.0f }; extern "C" float bhrt_cpu_fps(void) { return s_currentCpuFps.load(std::memory_order_acquire); } alignas(64) static float s_deflectBuf[2][DEFLECT_W * DEFLECT_H * 4]; static std::atomic s_deflectReadBuf{ 0 }; static std::atomic s_deflectReady{ true }; struct alignas(64) FrameUniforms { float camPos[3]; float view[9]; float orbitalLUT[256 * 4]; }; static FrameUniforms g_uniforms[2]; static std::atomic g_uniformWriteIdx{ 0 }; static std::atomic g_uniformReady{ false }; static std::mutex g_uniformMutex; static std::thread s_deflectThread; float snoise_cpu(float v_x, float v_y, float v_z) { const float C_x = 1.0f / 6.0f; const float C_y = 1.0f / 3.0f; float dot_v_Cyyy = (v_x + v_y + v_z) * C_y; float i_x = std::floor(v_x + dot_v_Cyyy); float i_y = std::floor(v_y + dot_v_Cyyy); float i_z = std::floor(v_z + dot_v_Cyyy); float dot_i_Cxxx = (i_x + i_y + i_z) * C_x; float x0_x = v_x - i_x + dot_i_Cxxx; float x0_y = v_y - i_y + dot_i_Cxxx; float x0_z = v_z - i_z + dot_i_Cxxx; float g_x = (x0_y >= x0_x) ? 1.0f : 0.0f; float g_y = (x0_z >= x0_y) ? 1.0f : 0.0f; float g_z = (x0_x >= x0_z) ? 1.0f : 0.0f; float l_x = 1.0f - g_x; float l_y = 1.0f - g_y; float l_z = 1.0f - g_z; float i1_x = std::min(g_x, l_z); float i1_y = std::min(g_y, l_x); float i1_z = std::min(g_z, l_y); float i2_x = std::max(g_x, l_z); float i2_y = std::max(g_y, l_x); float i2_z = std::max(g_z, l_y); float32x4_t i1 = { 0.0f, i1_x, i2_x, 1.0f }; float32x4_t i2 = { 0.0f, i1_y, i2_y, 1.0f }; float32x4_t i3 = { 0.0f, i1_z, i2_z, 1.0f }; float32x4_t vC_xxx = vdupq_n_f32(C_x); float32x4_t v_zero = vdupq_n_f32(0.0f); float32x4_t v_one = vdupq_n_f32(1.0f); float32x4_t c_mult = { 0.0f, 1.0f, 2.0f, 3.0f }; float32x4_t c_offset = vmulq_f32(c_mult, vC_xxx); float32x4_t dx = vsubq_f32(vdupq_n_f32(x0_x), i1); float32x4_t dy = vsubq_f32(vdupq_n_f32(x0_y), i2); float32x4_t dz = vsubq_f32(vdupq_n_f32(x0_z), i3); dx = vaddq_f32(dx, c_offset); dy = vaddq_f32(dy, c_offset); dz = vaddq_f32(dz, c_offset); float i_mod_x = i_x - std::floor(i_x * (1.0f / 289.0f)) * 289.0f; float i_mod_y = i_y - std::floor(i_y * (1.0f / 289.0f)) * 289.0f; float i_mod_z = i_z - std::floor(i_z * (1.0f / 289.0f)) * 289.0f; float32x4_t p_z = vaddq_f32(vdupq_n_f32(i_mod_z), i3); float32x4_t p_y = vaddq_f32(vdupq_n_f32(i_mod_y), i2); float32x4_t p_x = vaddq_f32(vdupq_n_f32(i_mod_x), i1); float32x4_t p = vpermute(vaddq_f32(vpermute(vaddq_f32(vpermute(p_z), p_y)), p_x)); float ns_z = 1.0f / 7.0f; float ns_x = ns_z * 2.0f; float32x4_t vns_z = vdupq_n_f32(ns_z); float32x4_t vns_x = vdupq_n_f32(ns_x); float32x4_t j_floor = vrndmq_f32(vmulq_f32(p, vdupq_n_f32(ns_z * ns_z))); float32x4_t j = vmlsq_f32(p, j_floor, vdupq_n_f32(49.0f)); float32x4_t x_ = vrndmq_f32(vmulq_f32(j, vns_z)); float32x4_t y_ = vrndmq_f32(vmlsq_f32(j, x_, vdupq_n_f32(7.0f))); float32x4_t gx = vmlaq_f32(vdupq_n_f32(-1.0f), x_, vns_x); float32x4_t gy = vmlaq_f32(vdupq_n_f32(-1.0f), y_, vns_x); float32x4_t h = vsubq_f32(vsubq_f32(v_one, vabsq_f32(gx)), vabsq_f32(gy)); float32x4_t b0_floor = vrndmq_f32(gx); float32x4_t b1_floor = vrndmq_f32(gy); float32x4_t s0 = vmlaq_f32(v_one, b0_floor, vdupq_n_f32(2.0f)); float32x4_t s1 = vmlaq_f32(v_one, b1_floor, vdupq_n_f32(2.0f)); uint32x4_t h_lt_zero = vcltq_f32(h, v_zero); float32x4_t sh = vbslq_f32(h_lt_zero, vdupq_n_f32(-1.0f), v_zero); float32x4_t ax = vmlaq_f32(gx, s0, sh); float32x4_t ay = vmlaq_f32(gy, s1, sh); float32x4_t dot_p = vmulq_f32(ax, ax); dot_p = vmlaq_f32(dot_p, ay, ay); dot_p = vmlaq_f32(dot_p, h, h); float32x4_t norm = vtaylorInvSqrt(dot_p); ax = vmulq_f32(ax, norm); ay = vmulq_f32(ay, norm); h = vmulq_f32(h, norm); float32x4_t dot_dx = vmulq_f32(dx, dx); dot_dx = vmlaq_f32(dot_dx, dy, dy); dot_dx = vmlaq_f32(dot_dx, dz, dz); float32x4_t m = vmaxq_f32(vsubq_f32(vdupq_n_f32(0.6f), dot_dx), v_zero); m = vmulq_f32(m, m); float32x4_t m4 = vmulq_f32(m, m); float32x4_t dot_p_dx = vmulq_f32(ax, dx); dot_p_dx = vmlaq_f32(dot_p_dx, ay, dy); dot_p_dx = vmlaq_f32(dot_p_dx, h, dz); float32x4_t final_vec = vmulq_f32(m4, dot_p_dx); float final_sum = vaddvq_f32(final_vec); return 42.0f * final_sum; } static void buildNoise3() { const int N = 64; std::vector data(N * N * N); for (int z = 0; z < N; z++) for (int y = 0; y < N; y++) for (int x = 0; x < N; x++) { float fx = float(x) / float(N); float fy = float(y) / float(N); float fz = float(z) / float(N); float v = snoise_cpu(fx * 16.0f, fy * 16.0f, fz * 16.0f); data[z * N * N + y * N + x] = (uint8_t)((v * 0.5f + 0.5f) * 255.0f); } glGenTextures(1, &s_noiseTex3D); glBindTexture(GL_TEXTURE_3D, s_noiseTex3D); glTexImage3D(GL_TEXTURE_3D, 0, GL_R8, N, N, N, 0, GL_RED, GL_UNSIGNED_BYTE, data.data()); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT); } static float s_orbitalLUT[256 * 2]; static GLuint s_orbitalLUTTex; static GLint loc_orbitalLUT; void updateOrbitalLUT(float t) { const float innerRadius = 2.6f; const float outerRadius = 12.0f; const float orbitalScale = 2.5f; int i = 0; for (; i <= 252; i += 4) { float32x4_t idx = { (float)i, (float)(i + 1), (float)(i + 2), (float)(i + 3) }; float32x4_t u = vmulq_n_f32(idx, 1.0f / 255.0f); float32x4_t r = vmlaq_n_f32(vdupq_n_f32(innerRadius), u, outerRadius - innerRadius); float32x4_t rsqrt_r = vrsqrteq_f32(r); rsqrt_r = vmulq_f32(rsqrt_r, vrsqrtsq_f32(vmulq_f32(r, rsqrt_r), rsqrt_r)); float32x4_t sqrt_r = vmulq_f32(r, rsqrt_r); float32x4_t r1_5 = vmulq_f32(r, sqrt_r); float32x4_t rcp = vrecpeq_f32(r1_5); rcp = vmulq_f32(rcp, vrecpsq_f32(r1_5, rcp)); float32x4_t speed = vmulq_n_f32(rcp, orbitalScale); float32x4_t angle = vmulq_n_f32(speed, t); float angles[4]; vst1q_f32(angles, angle); float cs[8]; fastSinCos(angles[0], &cs[1], &cs[0]); fastSinCos(angles[1], &cs[3], &cs[2]); fastSinCos(angles[2], &cs[5], &cs[4]); fastSinCos(angles[3], &cs[7], &cs[6]); s_orbitalLUT[(i + 0) * 2 + 0] = cs[0]; s_orbitalLUT[(i + 0) * 2 + 1] = cs[1]; s_orbitalLUT[(i + 1) * 2 + 0] = cs[2]; s_orbitalLUT[(i + 1) * 2 + 1] = cs[3]; s_orbitalLUT[(i + 2) * 2 + 0] = cs[4]; s_orbitalLUT[(i + 2) * 2 + 1] = cs[5]; s_orbitalLUT[(i + 3) * 2 + 0] = cs[6]; s_orbitalLUT[(i + 3) * 2 + 1] = cs[7]; } for (; i < 256; i++) { float u = i / 255.0f; float r = innerRadius + u * (outerRadius - innerRadius); float speed = orbitalScale / powf(fmaxf(r, 0.2f), 1.5f); float angle = t * speed; fastSinCos(angle, &s_orbitalLUT[i * 2 + 1], &s_orbitalLUT[i * 2 + 0]); } glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, s_orbitalLUTTex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 1, GL_RG, GL_FLOAT, s_orbitalLUT); } static inline float32x4x3_t neon_accel(float32x4_t h2, float32x4_t px, float32x4_t py, float32x4_t pz) { float32x4_t r2 = vmlaq_f32(vmlaq_f32(vmulq_f32(px, px), py, py), pz, pz); float32x4_t r4 = vmulq_f32(r2, r2); float32x4_t rsqrt_r = vrsqrteq_f32(r2); rsqrt_r = vmulq_f32(rsqrt_r, vrsqrtsq_f32(vmulq_f32(r2, rsqrt_r), rsqrt_r)); float32x4_t r2_5 = vmulq_f32(r4, vmulq_f32(r2, rsqrt_r)); float32x4_t rcp = vrecpeq_f32(r2_5); rcp = vmulq_f32(rcp, vrecpsq_f32(r2_5, rcp)); float32x4_t scale = vmulq_n_f32(vmulq_f32(h2, rcp), -1.5f); float32x4x3_t result; result.val[0] = vmulq_f32(scale, px); result.val[1] = vmulq_f32(scale, py); result.val[2] = vmulq_f32(scale, pz); return result; } static void traceDeflect4(float cpx, float cpy, float cpz, float32x4_t dx, float32x4_t dy, float32x4_t dz, float *outX, float *outY, float *outZ, float *outW, float *outDR, float *outDG, float *outDB, float *outDA, float dither) { float32x4_t px = vdupq_n_f32(cpx); float32x4_t py = vdupq_n_f32(cpy); float32x4_t pz = vdupq_n_f32(cpz); float32x4_t hx = vsubq_f32(vmulq_f32(py, dz), vmulq_f32(pz, dy)); float32x4_t hy = vsubq_f32(vmulq_f32(pz, dx), vmulq_f32(px, dz)); float32x4_t hz = vsubq_f32(vmulq_f32(px, dy), vmulq_f32(py, dx)); float32x4_t h2 = vmlaq_f32(vmlaq_f32(vmulq_f32(hx, hx), hy, hy), hz, hz); float diskR[4] = {}, diskG[4] = {}, diskB[4] = {}; float diskAlpha[4] = { 1.f, 1.f, 1.f, 1.f }; const float innerR = 2.6f, outerR = 12.0f, thinH = 0.18f, absorption = 1.2f; float32x4_t alive = vdupq_n_f32(1.0f); float32x4_t hitBH = vdupq_n_f32(0.0f); float32x4_t r2_init = vmlaq_f32(vmlaq_f32(vmulq_f32(px, px), py, py), pz, pz); float32x4_t rsqrt_r_init = vrsqrteq_f32(r2_init); rsqrt_r_init = vmulq_f32(rsqrt_r_init, vrsqrtsq_f32(vmulq_f32(r2_init, rsqrt_r_init), rsqrt_r_init)); float32x4_t dist_init = vmulq_f32(r2_init, rsqrt_r_init); float32x4_t initialStep = vminq_f32(vmaxq_f32(vmulq_n_f32(dist_init, 0.04f), vdupq_n_f32(0.02f)), vdupq_n_f32(0.3f)); initialStep = vmulq_n_f32(initialStep, dither); px = vaddq_f32(px, vmulq_f32(dx, initialStep)); py = vaddq_f32(py, vmulq_f32(dy, initialStep)); pz = vaddq_f32(pz, vmulq_f32(dz, initialStep)); for (int i = 0; i < 80; i++) { float32x4_t r2 = vmlaq_f32(vmlaq_f32(vmulq_f32(px, px), py, py), pz, pz); float32x4_t rsqrt_r = vrsqrteq_f32(r2); rsqrt_r = vmulq_f32(rsqrt_r, vrsqrtsq_f32(vmulq_f32(r2, rsqrt_r), rsqrt_r)); float32x4_t dist = vmulq_f32(r2, rsqrt_r); float32x4_t stepSize = vminq_f32(vmaxq_f32(vmulq_n_f32(dist, 0.04f), vdupq_n_f32(0.02f)), vdupq_n_f32(0.3f)); float32x4x3_t acc = neon_accel(h2, px, py, pz); dx = vaddq_f32(dx, vmulq_f32(acc.val[0], stepSize)); dy = vaddq_f32(dy, vmulq_f32(acc.val[1], stepSize)); dz = vaddq_f32(dz, vmulq_f32(acc.val[2], stepSize)); hx = vsubq_f32(vmulq_f32(py, dz), vmulq_f32(pz, dy)); hy = vsubq_f32(vmulq_f32(pz, dx), vmulq_f32(px, dz)); hz = vsubq_f32(vmulq_f32(px, dy), vmulq_f32(py, dx)); h2 = vmlaq_f32(vmlaq_f32(vmulq_f32(hx, hx), hy, hy), hz, hz); uint32x4_t insideEH = vcltq_f32(r2, vdupq_n_f32(1.0f)); uint32x4_t justDied = vandq_u32(insideEH, vreinterpretq_u32_f32(alive)); hitBH = vaddq_f32(hitBH, vreinterpretq_f32_u32(justDied)); alive = vreinterpretq_f32_u32(vbicq_u32(vreinterpretq_u32_f32(alive), insideEH)); px = vaddq_f32(px, vmulq_f32(vmulq_f32(dx, stepSize), alive)); py = vaddq_f32(py, vmulq_f32(vmulq_f32(dy, stepSize), alive)); pz = vaddq_f32(pz, vmulq_f32(vmulq_f32(dz, stepSize), alive)); { float pxs[4], pys[4], pzs[4], dxs[4], dys[4], dzs[4], ss[4], als[4]; vst1q_f32(pxs, px); vst1q_f32(pys, py); vst1q_f32(pzs, pz); vst1q_f32(dxs, dx); vst1q_f32(dys, dy); vst1q_f32(dzs, dz); vst1q_f32(ss, stepSize); vst1q_f32(als, alive); for (int lane = 0; lane < 4; lane++) { if (als[lane] < 0.5f || diskAlpha[lane] < 0.01f) continue; float px_ = pxs[lane], py_ = pys[lane], pz_ = pzs[lane]; float r_xz = sqrtf(px_ * px_ + pz_ * pz_); float density = 1.0f - sqrtf((px_ / outerR) * (px_ / outerR) + (py_ / thinH) * (py_ / thinH) + (pz_ / outerR) * (pz_ / outerR)); if (density < 0.001f || r_xz < innerR || r_xz > outerR) continue; float radT = (r_xz - innerR) / (outerR - innerR); float vertFade = 1.0f - fabsf(py_) / thinH; vertFade = vertFade * vertFade * vertFade * vertFade * vertFade * vertFade; density *= vertFade; if (density < 0.003f) continue; float speed = fminf(0.65f / sqrtf(fmaxf(r_xz, 2.f)), 0.6f); float vx = -pz_ / r_xz, vz = px_ / r_xz; float len_d = sqrtf(dxs[lane] * dxs[lane] + dys[lane] * dys[lane] + dzs[lane] * dzs[lane]); float cosT = -(dxs[lane] * vx + dzs[lane] * vz) / (len_d + 1e-8f); float gamma_ = 1.0f / sqrtf(1.0f - speed * speed); float doppler = 1.0f / (gamma_ * (1.0f - cosT * speed)); doppler = fmaxf(doppler, 0.001f); float beaming = doppler * doppler * doppler * doppler; float cr = (radT < 0.2f) ? (1.3f * (1.f - radT / 0.2f) + 1.0f * (radT / 0.2f)) : (1.0f * (1.f - (radT - 0.2f) / 0.8f) + 0.4f * (radT - 0.2f) / 0.8f); float cg = (radT < 0.2f) ? (1.1f * (1.f - radT / 0.2f) + 0.4f * (radT / 0.2f)) : (0.4f * (1.f - (radT - 0.2f) / 0.8f) + 0.02f * (radT - 0.2f) / 0.8f); float cb = (radT < 0.2f) ? (0.9f * (1.f - radT / 0.2f) + 0.05f * (radT / 0.2f)) : (0.05f * (1.f - (radT - 0.2f) / 0.8f) + 0.0f * (radT - 0.2f) / 0.8f); cr *= powf(doppler, 1.5f); cg *= powf(doppler, 1.5f); cb *= powf(doppler, 1.5f); float sampleAlpha = fminf(density * ss[lane] * absorption * beaming * 0.45f, 1.0f); float lit = 15.0f; diskR[lane] += cr * lit * sampleAlpha * diskAlpha[lane] * beaming; diskG[lane] += cg * lit * sampleAlpha * diskAlpha[lane] * beaming; diskB[lane] += cb * lit * sampleAlpha * diskAlpha[lane] * beaming; diskAlpha[lane] *= (1.0f - sampleAlpha); } } if (vmaxvq_u32(vreinterpretq_u32_f32(alive)) == 0) break; } float32x4_t len2 = vmlaq_f32(vmlaq_f32(vmulq_f32(dx, dx), dy, dy), dz, dz); float32x4_t rlen = vrsqrteq_f32(len2); rlen = vmulq_f32(rlen, vrsqrtsq_f32(vmulq_f32(len2, rlen), rlen)); vst1q_f32(outX, vmulq_f32(dx, rlen)); vst1q_f32(outY, vmulq_f32(dy, rlen)); vst1q_f32(outZ, vmulq_f32(dz, rlen)); uint32x4_t wasBH = vcgtq_f32(hitBH, vdupq_n_f32(0.0f)); float32x4_t exitW = vreinterpretq_f32_u32(vbicq_u32(vdupq_n_u32(0x3F800000u), wasBH)); vst1q_f32(outW, exitW); outDR[0] = diskR[0]; outDR[1] = diskR[1]; outDR[2] = diskR[2]; outDR[3] = diskR[3]; outDG[0] = diskG[0]; outDG[1] = diskG[1]; outDG[2] = diskG[2]; outDG[3] = diskG[3]; outDB[0] = diskB[0]; outDB[1] = diskB[1]; outDB[2] = diskB[2]; outDB[3] = diskB[3]; outDA[0] = diskAlpha[0]; outDA[1] = diskAlpha[1]; outDA[2] = diskAlpha[2]; outDA[3] = diskAlpha[3]; } static void deflectionWorkerFunc(const float camPos[3], const float view[9], float *deflectBuf, float *diskBuf, int startY, int endY) { float camDistW = sqrtf(camPos[0] * camPos[0] + camPos[1] * camPos[1] + camPos[2] * camPos[2]); float photonRadiusW = 12.0f / camDistW; float overlapRadius = photonRadiusW * 0.85f; float photonRadiusSqW = photonRadiusW * overlapRadius; const float aspectW = float(DEFLECT_W) / float(DEFLECT_H); for (int py_idx = 0; py_idx < DEFLECT_H; py_idx++) for (int px_idx = 0; px_idx < DEFLECT_W; px_idx += 4) { float cv = (py_idx + 0.5f) / DEFLECT_H - 0.5f; float cu = ((px_idx + 2.0f) / DEFLECT_W - 0.5f) * aspectW; if (cu * cu + cv * cv <= photonRadiusSqW) { int base = (py_idx * DEFLECT_W + px_idx) * 4; for (int k = 0; k < 4; k++) { deflectBuf[base + k * 4 + 0] = 0.0f; deflectBuf[base + k * 4 + 1] = 0.0f; deflectBuf[base + k * 4 + 2] = 0.0f; deflectBuf[base + k * 4 + 3] = 0.0f; diskBuf[base + k * 4 + 0] = 0.0f; diskBuf[base + k * 4 + 1] = 0.0f; diskBuf[base + k * 4 + 2] = 0.0f; diskBuf[base + k * 4 + 3] = 0.0f; } continue; } float uv[4][2]; for (int k = 0; k < 4; k++) { float u = ((px_idx + k + 0.5f) / DEFLECT_W) - 0.5f; float v = (py_idx + 0.5f) / DEFLECT_H - 0.5f; u *= aspectW; uv[k][0] = -u; uv[k][1] = v; } const float fov = 1.0f; float ldx[4], ldy[4], ldz[4]; for (int k = 0; k < 4; k++) { float lx = uv[k][0] * fov; float ly = uv[k][1] * fov; float lz = 1.0f; float len = sqrtf(lx * lx + ly * ly + lz * lz); lx /= len; ly /= len; lz /= len; ldx[k] = view[0] * lx + view[3] * ly + view[6] * lz; ldy[k] = view[1] * lx + view[4] * ly + view[7] * lz; ldz[k] = view[2] * lx + view[5] * ly + view[8] * lz; } float32x4_t dx4 = vld1q_f32(ldx); float32x4_t dy4 = vld1q_f32(ldy); float32x4_t dz4 = vld1q_f32(ldz); float dither = float(((px_idx * 73) + (py_idx * 101)) % 256) / 255.0f; float outX[4], outY[4], outZ[4], outW[4]; float outDR[4], outDG[4], outDB[4], outDA[4]; traceDeflect4(camPos[0], camPos[1], camPos[2], dx4, dy4, dz4, outX, outY, outZ, outW, outDR, outDG, outDB, outDA, dither); int base = (py_idx * DEFLECT_W + px_idx) * 4; for (int k = 0; k < 4; k++) { deflectBuf[base + k * 4 + 0] = outX[k]; deflectBuf[base + k * 4 + 1] = outY[k]; deflectBuf[base + k * 4 + 2] = outZ[k]; deflectBuf[base + k * 4 + 3] = outW[k]; diskBuf[base + k * 4 + 0] = outDR[k]; diskBuf[base + k * 4 + 1] = outDG[k]; diskBuf[base + k * 4 + 2] = outDB[k]; diskBuf[base + k * 4 + 3] = outDA[k]; } } } static void CPU_FPS_Start() { if (!running.load(std::memory_order_acquire)) return; s_frameStartTime = std::chrono::high_resolution_clock::now(); } static void CPU_FPS_End() { static auto lastFpsTime = std::chrono::high_resolution_clock::now(); static int frameCount = 0; static float timeAccumulator = 0.0f; auto now = std::chrono::high_resolution_clock::now(); float latency = std::chrono::duration(now - s_frameStartTime).count(); s_lastCpuLatencyMs.store(latency * 1000.0f, std::memory_order_release); float deltaFps = std::chrono::duration(now - lastFpsTime).count(); lastFpsTime = now; frameCount++; timeAccumulator += deltaFps; if (timeAccumulator >= 0.01f) { float fps = static_cast(frameCount) / timeAccumulator; s_currentCpuFps.store(fps, std::memory_order_release); frameCount = 0; timeAccumulator = 0.0f; } } static void makeFbo(GLuint &fbo, GLuint &tex, int w, int h) { glGenTextures(1, &tex); glBindTexture(GL_TEXTURE_2D, tex); glTexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F, w, h, 0, GL_RGB, GL_FLOAT, nullptr); 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } static void computeCamera_NEON(float t, float *outCamPos, float *outView) { float s = sinf(t * 0.1f); float c = cosf(t * 0.1f); float32x4_t eye = { -c * 15.f, s * 15.f, s * 15.f, 0.f }; float32x4_t target = vdupq_n_f32(0.f); float32x4_t worldUp = { 0.f, 1.f, 0.f, 0.f }; float32x4_t fwd = neon_normalize3(vsubq_f32(target, eye)); float32x4_t right = neon_normalize3(neon_cross3(fwd, worldUp)); float32x4_t newUp = neon_cross3(right, fwd); neon_store3(outCamPos, eye); // float32x4_t neg_fwd = vnegq_f32(fwd); neon_store3(outView + 0, right); neon_store3(outView + 3, newUp); neon_store3(outView + 6, fwd); } static void initTrace() { float initCamPos[3], initView[9]; computeCamera_NEON(0.0f, initCamPos, initView); const float aspect = float(DEFLECT_W) / float(DEFLECT_H); for (int row = 0; row < DEFLECT_H; row++) { for (int col = 0; col < DEFLECT_W; col += 4) { float ldx[4], ldy[4], ldz[4]; for (int k = 0; k < 4; k++) { float u = ((col + k + 0.5f) / DEFLECT_W - 0.5f) * aspect; float v = (row + 0.5f) / DEFLECT_H - 0.5f; float lx = -u, ly = v, lz = 1.0f; float len = sqrtf(lx * lx + ly * ly + lz * lz); lx /= len; ly /= len; lz /= len; ldx[k] = initView[0] * lx + initView[3] * ly + initView[6] * lz; ldy[k] = initView[1] * lx + initView[4] * ly + initView[7] * lz; ldz[k] = initView[2] * lx + initView[5] * ly + initView[8] * lz; } int base = (row * DEFLECT_W + col) * 4; for (int k = 0; k < 4; k++) { s_deflectBuf[0][base + k * 4 + 0] = ldx[k]; s_deflectBuf[0][base + k * 4 + 1] = ldy[k]; s_deflectBuf[0][base + k * 4 + 2] = ldz[k]; s_deflectBuf[0][base + k * 4 + 3] = 1.0f; } } } memcpy(s_deflectBuf[1], s_deflectBuf[0], sizeof(s_deflectBuf[0])); for (int i = 0; i < DEFLECT_W * DEFLECT_H; i++) { s_diskColorBuf[0][i * 4 + 0] = 0.0f; s_diskColorBuf[0][i * 4 + 1] = 0.0f; s_diskColorBuf[0][i * 4 + 2] = 0.0f; s_diskColorBuf[0][i * 4 + 3] = 1.0f; } memcpy(s_diskColorBuf[1], s_diskColorBuf[0], sizeof(s_diskColorBuf[0])); } static void deflectThreadFunc(int threadIdx, int coreID) { pinThread(coreID); uint32_t localFrame = 0; while (running.load(std::memory_order_acquire)) { { std::unique_lock lock(workMutex); workCV.wait(lock, [&] { return s_targetFrame.load(std::memory_order_acquire) > localFrame || !running.load(std::memory_order_acquire); }); } if (!running.load(std::memory_order_acquire)) break; localFrame = s_targetFrame.load(std::memory_order_acquire); int readIdx = g_uniformWriteIdx.load(std::memory_order_acquire); int writeIdx = s_deflectReadBuf.load(std::memory_order_acquire) ^ 1; int halfHeight = DEFLECT_H / 2; int startY = halfHeight * threadIdx; int endY = startY + halfHeight; deflectionWorkerFunc(g_uniforms[readIdx].camPos, g_uniforms[readIdx].view, s_deflectBuf[writeIdx], s_diskColorBuf[writeIdx], startY, endY); if (s_threadsFinished.fetch_add(1, std::memory_order_acq_rel) == 1) { CPU_FPS_End(); s_deflectReadBuf.store(writeIdx, std::memory_order_release); s_deflectReady.store(true, std::memory_order_release); s_threadsFinished.store(0, std::memory_order_release); } } } void BHRTSceneInit() { pinThread(0); GLint vsh = createAndCompileShader(GL_VERTEX_SHADER, vertexShaderSource); GLint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource); running = true; s_program = glCreateProgram(); glAttachShader(s_program, vsh); glAttachShader(s_program, fsh); glLinkProgram(s_program); GLuint tex1loc = glGetUniformLocation(s_program, "galaxy"); GLuint tex2loc = glGetUniformLocation(s_program, "colorMap"); loc_time = glGetUniformLocation(s_program, "time"); resloc = glGetUniformLocation(s_program, "res"); s_noiseTexLoc = glGetUniformLocation(s_program, "noiseTex"); buildNoise3(); GLint success; glGetProgramiv(s_program, GL_LINK_STATUS, &success); if (!success) { char buf[512]; glGetProgramInfoLog(s_program, sizeof(buf), nullptr, buf); TRACE("Link error: %s", buf); } glDeleteShader(vsh); glDeleteShader(fsh); float verts[] = { -1, -1, 1, -1, 1, 1, -1, -1, 1, 1, -1, 1 }; glGenVertexArrays(1, &s_vao); glBindVertexArray(s_vao); glGenBuffers(1, &s_vbo); glBindBuffer(GL_ARRAY_BUFFER, s_vbo); glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glEnableVertexAttribArray(0); int width, height, nchan; stbi_set_flip_vertically_on_load(true); glGenTextures(1, &tex1); glBindTexture(GL_TEXTURE_CUBE_MAP, tex1); stbi_uc *img = stbi_load_from_memory((const stbi_uc *)sk_png, sk_png_size, &width, &height, &nchan, 4); for (int f = 0; f < 6; f++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + f, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img); } 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); stbi_image_free(img); glGenTextures(1, &tex2); glBindTexture(GL_TEXTURE_2D, tex2); 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); img = stbi_load_from_memory((const stbi_uc *)colormap_png, colormap_png_size, &width, &height, &nchan, 4); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img); stbi_image_free(img); glGenTextures(1, &s_orbitalLUTTex); glBindTexture(GL_TEXTURE_2D, s_orbitalLUTTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, 256, 1, 0, GL_RG, GL_FLOAT, nullptr); 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); initTrace(); glGenTextures(1, &s_deflectionTex); glBindTexture(GL_TEXTURE_2D, s_deflectionTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, DEFLECT_W, DEFLECT_H, 0, GL_RGBA, GL_FLOAT, s_deflectBuf[0]); 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glUseProgram(s_program); s_deflectThreads[0] = std::thread(deflectThreadFunc, 0, 1); s_deflectThreads[1] = std::thread(deflectThreadFunc, 1, 2); loc_camPos = glGetUniformLocation(s_program, "camPos"); loc_view = glGetUniformLocation(s_program, "view"); s_deflectionTexLoc = glGetUniformLocation(s_program, "deflectionMap"); s_diskColorTexLoc = glGetUniformLocation(s_program, "diskColorMap"); glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D, s_deflectionTex); glUniform1i(s_deflectionTexLoc, 4); glUniform1i(tex2loc, 1); glGenTextures(1, &s_diskColorTex); glActiveTexture(GL_TEXTURE5); glBindTexture(GL_TEXTURE_2D, s_diskColorTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, DEFLECT_W, DEFLECT_H, 0, GL_RGBA, GL_FLOAT, s_diskColorBuf[0]); 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_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glUniform1i(s_diskColorTexLoc, 5); // auto projMtx = glm::perspective(glm::radians(40.0f), 1280.0f / 720.0f, 0.01f, 1000.0f); s_startTicks = armGetSystemTick(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, tex1); glUniform1i(tex1loc, 0); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, tex2); glUniform1i(tex2loc, 1); loc_orbitalLUT = glGetUniformLocation(s_program, "orbitalLUT"); glUniform1i(loc_orbitalLUT, 3); s_lastFrameTime = s_startTicks; s_fpsUpdateTime = s_startTicks; s_frameCount = 0; initTextRenderer(); makeFbo(s_sceneFbo, s_sceneTex, 1280, 720); makeFbo(s_bloomFboA, s_bloomTexA, BLOOM_W, BLOOM_H); makeFbo(s_bloomFboB, s_bloomTexB, BLOOM_W, BLOOM_H); GLuint bvsh = createAndCompileShader(GL_VERTEX_SHADER, rt_vs); auto linkBloom = [&](const char *fs) -> GLuint { GLuint fsh = createAndCompileShader(GL_FRAGMENT_SHADER, fs); GLuint prog = glCreateProgram(); glAttachShader(prog, bvsh); glAttachShader(prog, fsh); glLinkProgram(prog); glDeleteShader(fsh); GLint ok; glGetProgramiv(prog, GL_LINK_STATUS, &ok); if (!ok) { char buf[512]; glGetProgramInfoLog(prog, sizeof buf, nullptr, buf); FILE *f = fopen("/switch/bhrt_err.txt", "a"); if (f) { fprintf(f, "Bloom Link:\n%s\n", buf); fclose(f); } } return prog; }; s_bloomExtractProg = linkBloom(bloom_extract_fs); s_bloomBlurProg = linkBloom(bloom_blur_fs); s_bloomCompositeProg = linkBloom(bloom_composite_fs); glDeleteShader(bvsh); glUseProgram(s_bloomExtractProg); glUniform1i(glGetUniformLocation(s_bloomExtractProg, "sceneTex"), 0); glUniform1f(glGetUniformLocation(s_bloomExtractProg, "threshold"), 0.7f); glUseProgram(s_bloomBlurProg); glUniform1i(glGetUniformLocation(s_bloomBlurProg, "blurTex"), 0); s_blur_dirLoc = glGetUniformLocation(s_bloomBlurProg, "direction"); s_blur_texelLoc = glGetUniformLocation(s_bloomBlurProg, "texelSize"); glUniform2f(s_blur_texelLoc, 1.0f / BLOOM_W, 1.0f / BLOOM_H); glUseProgram(s_bloomCompositeProg); glUniform1i(glGetUniformLocation(s_bloomCompositeProg, "sceneTex"), 0); glUniform1i(glGetUniformLocation(s_bloomCompositeProg, "bloomTex"), 1); s_composite_bloomStrengthLoc = glGetUniformLocation(s_bloomCompositeProg, "bloomStrength"); glUniform1f(s_composite_bloomStrengthLoc, 2.4f); makeFbo(s_prevSceneFbo, s_prevSceneTex, 1280, 720); GLuint resolve_vsh = createAndCompileShader(GL_VERTEX_SHADER, rt_vs); GLuint resolve_fsh = createAndCompileShader(GL_FRAGMENT_SHADER, checkerboard_resolve_fs); s_resolveProg = glCreateProgram(); glAttachShader(s_resolveProg, resolve_vsh); glAttachShader(s_resolveProg, resolve_fsh); glLinkProgram(s_resolveProg); glDeleteShader(resolve_vsh); glDeleteShader(resolve_fsh); { GLint ok; glGetProgramiv(s_resolveProg, GL_LINK_STATUS, &ok); if (!ok) { char buf[512]; glGetProgramInfoLog(s_resolveProg, sizeof(buf), nullptr, buf); FILE *f = fopen("/switch/bhrt_err.txt", "a"); if (f) { fprintf(f, "RESOLVE LINK:\n%s\n", buf); fclose(f); } } } glUseProgram(s_resolveProg); glUniform1i(glGetUniformLocation(s_resolveProg, "currentTex"), 0); glUniform1i(glGetUniformLocation(s_resolveProg, "previousTex"), 1); glUniform2f(glGetUniformLocation(s_resolveProg, "texelSize"), 1.0f / 1280.0f, 1.0f / 720.0f); s_resolve_frameIndexLoc = glGetUniformLocation(s_resolveProg, "frameIndex"); glUseProgram(s_program); s_rt_frameIndexLoc = glGetUniformLocation(s_program, "frameIndex"); glBindFramebuffer(GL_FRAMEBUFFER, s_prevSceneFbo); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glBindFramebuffer(GL_FRAMEBUFFER, 0); glUseProgram(s_program); } float getTime5() { u64 elapsed = armGetSystemTick() - s_startTicks; return (elapsed * 625 / 12) / 2000000000.0; } void BHRTRender() { u64 currentTime = armGetSystemTick(); s_frameCount++; u64 timeSinceUpdate = currentTime - s_fpsUpdateTime; float secondsSinceUpdate = (timeSinceUpdate * 625.0f / 12.0f) / 1000000000.0f; if (secondsSinceUpdate >= 0.01f) { s_fps = s_frameCount / secondsSinceUpdate; s_frameCount = 0; s_fpsUpdateTime = currentTime; } glBindVertexArray(s_vao); float t = getTime5(); float camPos[3]; float view[9]; computeCamera_NEON(t, camPos, view); float camDist = sqrtf(camPos[0] * camPos[0] + camPos[1] * camPos[1] + camPos[2] * camPos[2]); float photonRadius = 12.0f / camDist; { std::lock_guard lk(workMutex); int wi = g_uniformWriteIdx.load(std::memory_order_acquire) ^ 1; memcpy(g_uniforms[wi].camPos, camPos, sizeof(camPos)); memcpy(g_uniforms[wi].view, view, sizeof(view)); g_uniformWriteIdx.store(wi, std::memory_order_release); s_targetFrame.fetch_add(1, std::memory_order_release); } CPU_FPS_Start(); workCV.notify_all(); updateOrbitalLUT(t); glBindFramebuffer(GL_FRAMEBUFFER, s_sceneFbo); glViewport(0, 0, 1280, 720); glUseProgram(s_program); glUniform1f(glGetUniformLocation(s_program, "photonScreenRadius"), photonRadius); glUniform3fv(loc_camPos, 1, camPos); glUniformMatrix3fv(loc_view, 1, GL_FALSE, view); glUniform1i(s_rt_frameIndexLoc, s_frameIndex); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, tex1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, tex2); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_3D, s_noiseTex3D); glUniform1i(s_noiseTexLoc, 2); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_2D, s_orbitalLUTTex); glUniform1f(loc_time, t); glUniform2f(resloc, RenderX, RenderY); glActiveTexture(GL_TEXTURE4); glBindTexture(GL_TEXTURE_2D, s_deflectionTex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, DEFLECT_W, DEFLECT_H, GL_RGBA, GL_FLOAT, s_deflectBuf[0]); glActiveTexture(GL_TEXTURE5); glBindTexture(GL_TEXTURE_2D, s_diskColorTex); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, DEFLECT_W, DEFLECT_H, GL_RGBA, GL_FLOAT, s_diskColorBuf[0]); glDrawArrays(GL_TRIANGLES, 0, 6); glBindFramebuffer(GL_FRAMEBUFFER, s_prevSceneFbo); glViewport(0, 0, 1280, 720); glUseProgram(s_resolveProg); glUniform1i(s_resolve_frameIndexLoc, s_frameIndex); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, s_sceneTex); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, s_prevSceneTex); glDrawArrays(GL_TRIANGLES, 0, 6); glBindFramebuffer(GL_FRAMEBUFFER, s_bloomFboA); glViewport(0, 0, BLOOM_W, BLOOM_H); glUseProgram(s_bloomExtractProg); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, s_prevSceneTex); glDrawArrays(GL_TRIANGLES, 0, 6); glUseProgram(s_bloomBlurProg); const int BLUR_ITERATIONS = 8; for (int i = 0; i < BLUR_ITERATIONS; i++) { glBindFramebuffer(GL_FRAMEBUFFER, s_bloomFboB); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, s_bloomTexA); glUniform2f(s_blur_dirLoc, 1.0f, 0.0f); glDrawArrays(GL_TRIANGLES, 0, 6); glBindFramebuffer(GL_FRAMEBUFFER, s_bloomFboA); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, s_bloomTexB); glUniform2f(s_blur_dirLoc, 0.0f, 1.0f); glDrawArrays(GL_TRIANGLES, 0, 6); } glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, 1280, 720); glUseProgram(s_bloomCompositeProg); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, s_prevSceneTex); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, s_bloomTexA); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); char fpsText[32]; snprintf(fpsText, sizeof(fpsText), "%.3f", s_fps); drawText(fpsText, -0.95f, 0.90f, 0.02f, 1.0f, 0.0f, 0.0f); char CPUFPS[64]; snprintf(CPUFPS, sizeof(CPUFPS), "%.3f", s_currentCpuFps.load(std::memory_order_acquire)); drawText(CPUFPS, 0.00f, 0.90f, 0.02f, 1.0f, 0.0f, 0.0f); s_frameIndex ^= 1; } void BHRTExit() { cleanupTextRenderer(); glDeleteFramebuffers(1, &s_prevSceneFbo); glDeleteTextures(1, &s_prevSceneTex); glDeleteProgram(s_resolveProg); glDeleteBuffers(1, &s_vbo); glDeleteVertexArrays(1, &s_vao); glDeleteProgram(s_program); glDeleteTextures(1, &s_orbitalLUTTex); running.store(false, std::memory_order_release); workCV.notify_all(); if (s_deflectThreads[0].joinable()) s_deflectThreads[0].join(); if (s_deflectThreads[1].joinable()) s_deflectThreads[1].join(); } int BHRTMain(int arcg, char *argv[]) { setMesaConfig(); if (!initEgl(nwindowGetDefault())) return EXIT_FAILURE; gladLoadGLLoader((GLADloadproc)eglGetProcAddress); BHRTSceneInit(); padConfigureInput(1, HidNpadStyleSet_NpadStandard); PadState pad; padInitializeDefault(&pad); while (appletMainLoop()) { padUpdate(&pad); u32 kDown = padGetButtonsDown(&pad); if (kDown & HidNpadButton_B) { state = STATE_MENU; break; } BHRTRender(); eglSwapBuffers(s_display, s_surface); } BHRTExit(); deinitEgl(); return EXIT_SUCCESS; }