#include "LunarWM_render.h"

#include "LunarWM_core.h"
#include "LunarWM_xr.h"
#include "common.h"
#include "vec.h"

#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

static inline SphericalCoord get_forward_spherical_with_nearest(
    Vector3 fwd, float r)
{
	if (fabs(fwd.y) < 0.2f) {
		fwd.y = 0;
	}
	Vector3 vec = Vector3Scale(Vector3Normalize(fwd), r);
	return Vector3ToSpherical(vec);
}

static inline Matrix xr_matrix(XrPosef const pose)
{
	Matrix const translation
	    = MatrixTranslate(pose.position.x, pose.position.y, pose.position.z);
	Matrix const rotation = QuaternionToMatrix((Quaternion) {
	    pose.orientation.x,
	    pose.orientation.y,
	    pose.orientation.z,
	    pose.orientation.w,
	});
	return MatrixMultiply(rotation, translation);
}

static inline Matrix xr_projection_matrix(XrFovf const fov)
{
	static_assert(RL_CULL_DISTANCE_FAR > RL_CULL_DISTANCE_NEAR);

	Matrix matrix = {};

	auto const near = (float)RL_CULL_DISTANCE_NEAR;
	auto const far = (float)RL_CULL_DISTANCE_FAR;

	float const tan_angle_left = tanf(fov.angleLeft);
	float const tan_angle_right = tanf(fov.angleRight);

	float const tan_angle_down = tanf(fov.angleDown);
	float const tan_angle_up = tanf(fov.angleUp);

	float const tan_angle_width = tan_angle_right - tan_angle_left;
	float const tan_angle_height = tan_angle_up - tan_angle_down;

	matrix.m0 = 2 / tan_angle_width;
	matrix.m4 = 0;
	matrix.m8 = (tan_angle_right + tan_angle_left) / tan_angle_width;
	matrix.m12 = 0;

	matrix.m1 = 0;
	matrix.m5 = 2 / tan_angle_height;
	matrix.m9 = (tan_angle_up + tan_angle_down) / tan_angle_height;
	matrix.m13 = 0;

	matrix.m2 = 0;
	matrix.m6 = 0;
	matrix.m10 = -(far + near) / (far - near);
	matrix.m14 = -(far * (near + near)) / (far - near);

	matrix.m3 = 0;
	matrix.m7 = 0;
	matrix.m11 = -1;
	matrix.m15 = 0;

	return matrix;
}

static void DrawBillboardNoShear(
    Camera3D const cam, Texture2D tex, Vector3 pos, float scale, Color tint)
{
	Rectangle const src = { 0, 0, tex.width, tex.height };

	Vector2 const size = { scale * fabsf(src.width / src.height), -scale };
	Vector2 const origin = { size.x * 0.5f, size.y * 0.5f };

	DrawBillboardPro(cam, tex, src, pos, cam.up, size, origin, 0.0f, tint);
}

void DrawTextureCyl(
    Texture2D tex, Vector3 center, float radius, float scale, bool y_flip)
{
	if (!tex.id || scale <= 0.0f || radius == 0.0f)
		return;

	float r = fabsf(radius);
	float arc_len = (float)tex.width * scale; // arc length in world units
	float theta = arc_len / r;                // radians across the panel
	float half_t = 0.5f * theta;
	float half_h = 0.5f * (float)tex.height * scale;

	// mid-angle around Y so the segment's middle sits at 'center'
	float a0 = atan2f(center.x, center.z);

	// shift so the cylinder surface midpoint matches 'center'
	Vector3 mid_ref = (Vector3) { sinf(a0) * r, center.y, cosf(a0) * r };
	Vector3 delta = Vector3Subtract(center, mid_ref);

	// tessellation: about 3° per slice (min 8)
	int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f));
	if (slices > 1024)
		slices = 1024;

	float vt = y_flip ? 1.0f : 0.0f;
	float vb = y_flip ? 0.0f : 1.0f;

	rlDrawRenderBatchActive(); // flush any prior state
	rlSetTexture(tex.id);
	rlDisableBackfaceCulling();
	rlColor4ub(255, 255, 255, 255);
	rlBegin(RL_QUADS);

	for (int i = 0; i < slices; ++i) {
		float u0 = (float)i / (float)slices;
		float u1 = (float)(i + 1) / (float)slices;

		float aL = a0 - half_t + theta * u0;
		float aR = a0 - half_t + theta * u1;

		Vector3 nL = (Vector3) { sinf(aL), 0.0f, cosf(aL) };
		Vector3 nR = (Vector3) { sinf(aR), 0.0f, cosf(aR) };
		if (radius < 0.0f) {
			nL = Vector3Negate(nL);
			nR = Vector3Negate(nR);
		}

		Vector3 pLT = Vector3Add(
		    (Vector3) { nL.x * r, center.y + half_h, nL.z * r }, delta);
		Vector3 pLB = Vector3Add(
		    (Vector3) { nL.x * r, center.y - half_h, nL.z * r }, delta);
		Vector3 pRT = Vector3Add(
		    (Vector3) { nR.x * r, center.y + half_h, nR.z * r }, delta);
		Vector3 pRB = Vector3Add(
		    (Vector3) { nR.x * r, center.y - half_h, nR.z * r }, delta);

		// match your flat-quad U flip (so WL textures look correct)
		float U0 = 1.0f - u0;
		float U1 = 1.0f - u1;

		// one normal per-vertex (simple cylindrical)
		rlNormal3f(nL.x, nL.y, nL.z);
		rlTexCoord2f(U0, vt);
		rlVertex3f(pLT.x, pLT.y, pLT.z);
		rlNormal3f(nR.x, nR.y, nR.z);
		rlTexCoord2f(U1, vt);
		rlVertex3f(pRT.x, pRT.y, pRT.z);
		rlNormal3f(nR.x, nR.y, nR.z);
		rlTexCoord2f(U1, vb);
		rlVertex3f(pRB.x, pRB.y, pRB.z);
		rlNormal3f(nL.x, nL.y, nL.z);
		rlTexCoord2f(U0, vb);
		rlVertex3f(pLB.x, pLB.y, pLB.z);
	}

	rlEnd();
	rlSetTexture(0);
	rlEnableBackfaceCulling();
}

static void DrawTextureCyl2(Texture2D tex, Vector3 sphere_center,
    SphericalCoord coord, float rad, float scale, bool y_flip)
{
	if (!tex.id || scale <= 0.0f || rad == 0.0f)
		return;

	// midpoint on the sphere where the panel should sit (its center)
	Vector3 fwd = SphericalToVector3(coord);
	if (Vector3Length(fwd) < 1e-6f)
		fwd = (Vector3) { 0, 0, 1 };
	fwd = Vector3Normalize(fwd);

	// build a local tangent frame at that point (right, up, forward)
	Vector3 worldUp = (Vector3) { 0, 1, 0 };
	if (fabsf(Vector3DotProduct(worldUp, fwd)) > 0.99f)
		worldUp = (Vector3) { 1, 0, 0 };
	Vector3 right = Vector3Normalize(Vector3CrossProduct(worldUp, fwd));
	Vector3 up = Vector3Normalize(Vector3CrossProduct(fwd, right));

	float r = fabsf(rad);
	float arc_len = (float)tex.width * scale; // world units across
	float theta = arc_len / r;                // total horizontal FOV in radians
	float half_t = 0.5f * theta;
	float half_h = 0.5f * (float)tex.height * scale;

	// shift so cylinder's surface midpoint lands exactly at coord.r from
	// sphere_center
	Vector3 delta = Vector3Add(sphere_center,
	    Vector3Add(Vector3Scale(fwd, coord.r - r), (Vector3) { 0, 0, 0 }));

	// tessellation: about 3° per slice (min 8, max 1024)
	int slices = (int)ceilf(fmaxf(theta * (180.0f / PI) / 3.0f, 8.0f));
	if (slices > 1024)
		slices = 1024;

	float vt = y_flip ? 1.0f : 0.0f;
	float vb = y_flip ? 0.0f : 1.0f;

	rlDrawRenderBatchActive();
	rlSetTexture(tex.id);
	rlDisableBackfaceCulling();
	rlColor4ub(255, 255, 255, 255);
	rlBegin(RL_QUADS);

	for (int i = 0; i < slices; ++i) {
		float u0 = (float)i / (float)slices;
		float u1 = (float)(i + 1) / (float)slices;

		float aL = -half_t + theta * u0;
		float aR = -half_t + theta * u1;

		// local outward directions on the cylindrical surface
		Vector3 nL = Vector3Add(
		    Vector3Scale(right, sinf(aL)), Vector3Scale(fwd, cosf(aL)));
		Vector3 nR = Vector3Add(
		    Vector3Scale(right, sinf(aR)), Vector3Scale(fwd, cosf(aR)));

		if (rad < 0.0f) {
			nL = Vector3Negate(nL);
			nR = Vector3Negate(nR);
		}

		// surface points (center band), then top/bottom by +/- up*half_h
		Vector3 cL = Vector3Add(delta, Vector3Scale(nL, r));
		Vector3 cR = Vector3Add(delta, Vector3Scale(nR, r));

		Vector3 pLT = Vector3Add(cL, Vector3Scale(up, half_h));
		Vector3 pLB = Vector3Add(cL, Vector3Scale(up, -half_h));
		Vector3 pRT = Vector3Add(cR, Vector3Scale(up, half_h));
		Vector3 pRB = Vector3Add(cR, Vector3Scale(up, -half_h));

		// match the original horizontal flip so Wayland textures look correct
		float U0 = 1.0f - u0;
		float U1 = 1.0f - u1;

		rlNormal3f(nL.x, nL.y, nL.z);
		rlTexCoord2f(U0, vt);
		rlVertex3f(pLT.x, pLT.y, pLT.z);

		rlNormal3f(nR.x, nR.y, nR.z);
		rlTexCoord2f(U1, vt);
		rlVertex3f(pRT.x, pRT.y, pRT.z);

		rlNormal3f(nR.x, nR.y, nR.z);
		rlTexCoord2f(U1, vb);
		rlVertex3f(pRB.x, pRB.y, pRB.z);

		rlNormal3f(nL.x, nL.y, nL.z);
		rlTexCoord2f(U0, vb);
		rlVertex3f(pLB.x, pLB.y, pLB.z);
	}

	rlEnd();
	rlSetTexture(0);
	rlEnableBackfaceCulling();
}

static inline Vector3 RecenterPoint(LunarWM *wm, Vector3 p)
{
	if (!wm->xr.recenter_active)
		return p;
	return Vector3Add(Vector3RotateByQuaternion(p, wm->xr.recenter_rot),
	    wm->xr.recenter_trans);
}

static inline Quaternion RecenterOrient(LunarWM *wm, Quaternion q)
{
	if (!wm->xr.recenter_active)
		return q;
	return QuaternionMultiply(wm->xr.recenter_rot, q);
}

static LunarWM_Toplevel *find_toplevel(LunarWM *this, int id)
{
	for (size_t i = 0; i < vector_size(this->wayland.v_toplevels); i++) {
		auto *tl = this->wayland.v_toplevels[i];
		if (tl->id == id)
			return tl;
	}

	return NULL;
}

void LunarWM_render_hud(LunarWM *this, float /*dt*/, int hud_size)
{
	ClearBackground((Color) { 0, 0, 0, 0 });

	float const text_size = this->cman->cfg.displays.hud.font_size;

	char const *txt
	    = TextFormat("WAYLAND_DISPLAY=%s", getenv("WAYLAND_DISPLAY"));
	auto txt_w = MeasureText(txt, 24);
	DrawText(
	    txt, hud_size / 2 - txt_w / 2, hud_size - text_size, text_size, WHITE);

	txt = TextFormat("DISPLAY=%s", getenv("DISPLAY"));
	txt_w = MeasureText(txt, text_size);
	DrawText(txt, hud_size / 2 - txt_w / 2, hud_size - text_size * 2, text_size,
	    WHITE);

	{
		time_t t = time(NULL);
		struct tm *tm_info = localtime(&t);

		int hours = tm_info->tm_hour;
		int minutes = tm_info->tm_min;
		txt = TextFormat("%02d:%02d", hours, minutes);
		txt_w = MeasureText(txt, 32);
		DrawText(txt, hud_size / 2 - txt_w / 2, 0, text_size, WHITE);
	}
}

void LunarWM_render_windows(LunarWM *this, bool alpha_check)
{
	for (size_t i = 0; i
	    < vector_size(this->wm.workspaces[this->wm.active_workspace].v_windows);
	    i++) {
		auto *window
		    = &this->wm.workspaces[this->wm.active_workspace].v_windows[i];
		auto *tl = window->tl;
		if (!tl || !tl->surface) {
			continue;
		}
		if (tl->gles_texture) {
			if (alpha_check && tl->composed_has_alpha) {
				continue;
			}
			Texture2D tex = tl->rl_texture;
			bool y_flip = false;
			if (IsRenderTextureValid(tl->surface_rt)) {
				tex = tl->surface_rt.texture;
				tex.width = tl->rl_texture.width;
				tex.height = tl->rl_texture.height;
				y_flip = true;
			}
			if (!tex.id)
				continue;
			float rad = window->coord.r - 0.01f * (float)i;
			DrawTextureCyl2(tex, Vector3Zero(), window->coord, rad,
			    this->cman->cfg.space.window_scale, y_flip);
		}
	}
}

void LunarWM_render_3d(LunarWM *this, float /*dt*/)
{
	LunarWM_render_windows(this, true);

	for (int h = 0; this->xr.hand_tracking_enabled && h < 2; ++h) {
		auto *hand_info = &this->xr.hands[h];
		if (hand_info->hand_tracker == XR_NULL_HANDLE)
			continue;
		for (size_t k = 0; k < XR_HAND_JOINT_COUNT_EXT; ++k) {
			auto const *jl = &hand_info->joint_locations[k];
			Vector3 pos = {
				jl->pose.position.x,
				jl->pose.position.y,
				jl->pose.position.z,
			};
			pos = RecenterPoint(this, pos);
			DrawSphere(pos, jl->radius, (Color) { 255, 0, 0, 255 });
		}
	}

	Skybox_draw(this->renderer.skybox, this->renderer.camera.position);

	rlEnableColorBlend();
	rlDisableDepthMask(); // don't write depth
	LunarWM_render_windows(this, false);
	// TODO: Replace with actual cursor texture.
	{ // Cursor
		rlDrawRenderBatchActive();
		rlDisableDepthTest();
		Vector3 tip = SphericalToVector3(this->wm.pointer);

		Vector3 n = Vector3Normalize(
		    Vector3Subtract(this->renderer.camera.position, tip));
		Vector3 up_hint = (Vector3) { 0, 1, 0 };
		if (fabsf(Vector3DotProduct(up_hint, n)) > 0.98f)
			up_hint = (Vector3) { 1, 0, 0 };
		Vector3 right = Vector3Normalize(Vector3CrossProduct(up_hint, n));
		Vector3 up = Vector3Normalize(Vector3CrossProduct(n, right));
		Vector3 down = Vector3Negate(up);

		float const s = 0.03f;

		Vector3 v1 = tip;
		Vector3 v2 = Vector3Add(tip, Vector3Scale(down, s));
		Vector3 v3 = Vector3Add(tip, Vector3Scale(right, s));

		Vector3 normal = Vector3CrossProduct(
		    Vector3Subtract(v2, v1), Vector3Subtract(v3, v1));
		if (Vector3DotProduct(normal, n) < 0.0f) {
			Vector3 tmp = v2;
			v2 = v3;
			v3 = tmp;
		}

		DrawTriangle3D(v1, v2, v3, RED);
		rlDrawRenderBatchActive();
		rlEnableDepthTest();
	}
	rlEnableDepthMask();

	if (IsTextureValid(this->renderer.hud_rt.texture)) {
		rlDrawRenderBatchActive();
		rlDisableDepthTest();

		Vector3 camPos = this->renderer.camera.position;
		Vector3 camDir = Vector3Normalize(
		    Vector3Subtract(this->renderer.camera.target, camPos));
		Vector3 up = this->renderer.camera.up;
		Vector3 right = Vector3Normalize(Vector3CrossProduct(camDir, up));
		up = Vector3CrossProduct(right, camDir);

		Vector3 center = Vector3Add(camPos, Vector3Scale(camDir, 0.6f));

		float heightMeters = 0.10f;

		DrawBillboardNoShear(this->renderer.camera,
		    this->renderer.hud_rt.texture, center, heightMeters * 5, WHITE);
		rlDrawRenderBatchActive();

		rlEnableDepthTest();
	}
}

bool LunarWM_render_layer(
    LunarWM *this, LunarWM_RenderLayerInfo *info, float dt)
{
	auto const view_count = (uint32_t)this->xr.view_configuration_views_count;
	assert(view_count == 2);

	XrView views[2] = {};
	for (int i = 0; i < ARRAY_SZ(views); i++) {
		views[i] = (XrView) {
			.type = XR_TYPE_VIEW,
		};
	}

	XrViewLocateInfo locInfo = {
		.type = XR_TYPE_VIEW_LOCATE_INFO,
		.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
		.displayTime = info->predicted_display_time,
		.space = this->xr.local_space,
	};

	XrViewState viewState = { .type = XR_TYPE_VIEW_STATE };
	uint32_t located = 0;
	if (xrLocateViews(
	        this->xr.session, &locInfo, &viewState, view_count, &located, views)
	        != XR_SUCCESS
	    || located != view_count) {
		wlr_log(WLR_ERROR, "Failed to locate views");
		return false;
	}

	// acquire swapchain images
	auto *color_sc = &this->xr.swapchains.v_color[0];
	auto *depth_sc = &this->xr.swapchains.v_depth[0];

	uint32_t col_idx = 0;
	uint32_t dep_idx = 0;
	if (!LunarWM_xr_acquire_wait(color_sc->swapchain, &col_idx)
	    || !LunarWM_xr_acquire_wait(depth_sc->swapchain, &dep_idx)) {
		wlr_log(WLR_ERROR, "Swap-chain acquire failed");
		return false;
	}

	GLuint color_tex = LunarWM_xr_get_swapchain_image(this, 0, col_idx);
	GLuint depth_tex = LunarWM_xr_get_swapchain_image(this, 1, dep_idx);

	// build FBO
	if (this->renderer.fbo == 0u) {
		glGenFramebuffers(1, &this->renderer.fbo);
	}
	glBindFramebuffer(GL_FRAMEBUFFER, this->renderer.fbo);
	rlFramebufferAttach(this->renderer.fbo, color_tex,
	    RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_TEXTURE2D, 0);
	rlFramebufferAttach(this->renderer.fbo, depth_tex, RL_ATTACHMENT_DEPTH,
	    RL_ATTACHMENT_TEXTURE2D, 0);
	assert(rlFramebufferComplete(this->renderer.fbo));

	uint32_t const eye_w
	    = this->xr.a_view_configuration_views[0].recommendedImageRectWidth;
	uint32_t const eye_h
	    = this->xr.a_view_configuration_views[0].recommendedImageRectHeight;

	this->renderer.tmp_rt = (RenderTexture2D) {
		.id = this->renderer.fbo,
		.texture = { color_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 },
		.depth = { depth_tex, (int)eye_w * view_count, (int)eye_h, 1, -1 },
	};

	// head-space view matrix (matches rlOpenXR)
	XrSpaceLocation headLoc = { .type = XR_TYPE_SPACE_LOCATION };
	xrLocateSpace(this->xr.view_space, this->xr.local_space,
	    info->predicted_display_time, &headLoc);
	auto const head_view = MatrixInvert(xr_matrix(headLoc.pose));

	// per-eye projection + view-offset
	Matrix const view_off_l
	    = MatrixMultiply(xr_matrix(views[0].pose), head_view);
	Matrix const view_off_r
	    = MatrixMultiply(xr_matrix(views[1].pose), head_view);

	Matrix const proj_r = xr_projection_matrix(views[0].fov);
	Matrix const proj_l = xr_projection_matrix(views[1].fov);

	int const hud_size = this->cman->cfg.displays.hud.size;
	if (!IsTextureValid(this->renderer.hud_rt.texture)) {
		this->renderer.hud_rt = LoadRenderTexture(hud_size, hud_size);
	}

	if (IsTextureValid(this->renderer.hud_rt.texture)) {
		BeginTextureMode(this->renderer.hud_rt);
		{
			LunarWM_render_hud(this, dt, hud_size);
		}
		EndTextureMode();
	}

	// draw

	if (!IsTextureValid(this->renderer.main_rt.texture)) {
		this->renderer.main_rt = LoadRenderTexture(eye_w * view_count, eye_h);
	}

	BeginTextureMode(this->renderer.main_rt);

	rlEnableStereoRender();
	rlSetMatrixProjectionStereo(proj_r, proj_l);
	rlSetMatrixViewOffsetStereo(view_off_r, view_off_l);

	glViewport(0, 0, (GLsizei)eye_w * view_count, (GLsizei)eye_h);
	rlClearColor(0, 0, 10, 255);
	rlClearScreenBuffers();

	for (int i = 0; i < 1; i++) {
		XrTime const time = info->predicted_display_time;

		XrSpaceLocation view_location = { .type = XR_TYPE_SPACE_LOCATION };
		XrResult const result = xrLocateSpace(
		    this->xr.view_space, this->xr.local_space, time, &view_location);
		if (result != XR_SUCCESS) {
			break;
		}

		if ((view_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT)
		    != 0u) {
			auto const pos = view_location.pose.position;
			this->renderer.camera.position = (Vector3) { pos.x, pos.y, pos.z };
		}
		if ((view_location.locationFlags
		        & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT)
		    != 0u) {
			auto const rot = view_location.pose.orientation;
			auto const forward
			    = Vector3RotateByQuaternion((Vector3) { 0, 0, -1 },
			        (Quaternion) { rot.x, rot.y, rot.z, rot.w });
			auto const up = Vector3RotateByQuaternion((Vector3) { 0, 1, 0 },
			    (Quaternion) { rot.x, rot.y, rot.z, rot.w });
			this->renderer.camera.target
			    = Vector3Add(this->renderer.camera.position, forward);
			this->renderer.camera.up = up;
		}

		if (this->xr.recenter_active) {
			Vector3 pos = this->renderer.camera.position;
			Vector3 fwd = Vector3Normalize(
			    Vector3Subtract(this->renderer.camera.target, pos));
			Vector3 up = this->renderer.camera.up;

			pos = Vector3Add(
			    Vector3RotateByQuaternion(pos, this->xr.recenter_rot),
			    this->xr.recenter_trans);
			fwd = Vector3RotateByQuaternion(fwd, this->xr.recenter_rot);
			up = Vector3RotateByQuaternion(up, this->xr.recenter_rot);

			this->renderer.camera.position = pos;
			this->renderer.camera.target = Vector3Add(pos, fwd);
			this->renderer.camera.up = up;
		}
	}

	BeginMode3D(this->renderer.camera);
	{
		ClearBackground(RED);
		LunarWM_render_3d(this, dt);
	}
	EndMode3D();

	rlDisableStereoRender();
	EndTextureMode();

	if (!IsShaderValid(this->renderer.linear_srgb)) {
		static char const linear_srgb[] = {
#embed "../assets/linear_srgb.fs"
			, 0
		};
		this->renderer.linear_srgb = LoadShaderFromMemory(NULL, linear_srgb);
	}

	BeginTextureMode(this->renderer.tmp_rt);
	rlDisableColorBlend();
	ClearBackground(BLACK);
	BeginShaderMode(this->renderer.linear_srgb);
	DrawTexturePro(this->renderer.main_rt.texture,
	    (Rectangle) {
	        0,
	        0,
	        this->renderer.main_rt.texture.width,
	        -this->renderer.main_rt.texture.height,
	    },
	    (Rectangle) {
	        0,
	        0,
	        this->renderer.tmp_rt.texture.width,
	        this->renderer.tmp_rt.texture.height,
	    },
	    (Vector2) { 0, 0 }, 0, WHITE);
	EndShaderMode();
	EndTextureMode();
	rlEnableColorBlend();

	// release swapchain images
	XrSwapchainImageReleaseInfo const ri
	    = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
	xrReleaseSwapchainImage(color_sc->swapchain, &ri);
	xrReleaseSwapchainImage(depth_sc->swapchain, &ri);

	// fill projection layer
	info->layer_projection_views_count = view_count;

	for (uint32_t i = 0; i < view_count; ++i) {
		int32_t const xOff = i * eye_w;
		auto *pv = &info->layer_projection_views[i];
		pv->pose = views[i].pose;
		pv->fov = views[i].fov;
		pv->subImage.swapchain = color_sc->swapchain;
		pv->subImage.imageRect.offset = (XrOffset2Di) { .x = xOff, .y = 0 };
		pv->subImage.imageRect.extent
		    = (XrExtent2Di) { .width = eye_w, .height = eye_h };
		pv->subImage.imageArrayIndex = 0;
	}

	info->layer_projection = (XrCompositionLayerProjection) {
		.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
		.layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT
		    | XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT,
		.space = this->xr.local_space,
		.viewCount = view_count,
		.views = info->layer_projection_views,
	};

	info->layers_count = 0;
	info->layers[info->layers_count++]
	    = (XrCompositionLayerBaseHeader *)&info->layer_projection;

	if (this->renderer.first_frame) {
		LunarWM_set_recenter_from_camera(this);
		this->renderer.first_frame = false;
		this->wm.pointer = get_forward_spherical_with_nearest(
		    this->renderer.camera.target, this->cman->cfg.space.radius);
	}

	return true;
}
