


/*

On macOS, make sure that the MACOS macro below is defined, and then compile with 
    clang 520mainBaseline.c -lglfw -lvulkan -rpath /usr/local/VulkanSDK/1.3.296.0/macOS/lib
(You might need to tweak that specific path based on your installation.) You 
might also need to compile the shaders with 
    glslc 520shader.vert -o 520vert.spv
    glslc 520shader.frag -o 520frag.spv
Then run the program with 
    ./a.out

On Linux, make sure that the MACOS macro below is undefined, and then do 
    clang 520mainBaseline.c -lglfw -lvulkan -lm
You might also need to compile the shaders with 
    /mnt/c/VulkanSDK/1.3.216.0/Bin/glslc.exe 520shader.vert -o 520vert.spv
    /mnt/c/VulkanSDK/1.3.216.0/Bin/glslc.exe 520shader.frag -o 520frag.spv
(You might have to change the SDK version number to match your installation.) 
Then run the program with 
    ./a.out

If you see errors, then try changing ANISOTROPY to 0 and/or MAXFRAMESINFLIGHT to 
1.

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <sys/time.h>
#include <math.h>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"



/*** CONFIGURATION ************************************************************/

/* Informational messages should (1) or shouldn't (0) be printed to stderr. */
#define VERBOSE 1

/* Anisotropic texture filtering. If you get an error that there are no suitable 
Vulkan devices, then try changing ANISOTROPY from 1 to 0. */
#define ANISOTROPY 1

/* A bound on the number of frames under construction at any given time. Leave 
it at 2 unless you have a good reason. If Vulkan throws an error about 
simultaneous use of a command buffer, then try changing it to 1. */
#define MAXFRAMESINFLIGHT 2

/* To disable validation, set NUMVALLAYERS to 0. Otherwise, we use the first 
NUMVALLAYERS layers specified below. */
#define NUMVALLAYERS 1
const char* valLayers[] = {
	"VK_LAYER_KHRONOS_validation"};

/* If you're on macOS, then this next line should be un-commented. If you're on 
Linux or Windows or WSL, then comment out this line. */
#define MACOS

/* We use the first NUMINSTANCEEXT instance extensions below and the first 
NUMDEVICEEXT device extensions below. */
#ifdef MACOS
#define NUMINSTANCEEXT 2
const char* instanceExtensions[] = {
	"VK_KHR_get_physical_device_properties2", 
    VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME};
#define NUMDEVICEEXT 2
const char* deviceExtensions[] = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME, 
    "VK_KHR_portability_subset"};
#else
#define NUMINSTANCEEXT 1
const char* instanceExtensions[] = {
	"VK_KHR_get_physical_device_properties2"};
#define NUMDEVICEEXT 1
const char* deviceExtensions[] = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME};
#endif



/*** INFRASTRUCTURE ***********************************************************/

/* Remember to read from globals as you like but write to globals only through 
their accessor functions. */
#include "440gui.c"
guiGUI gui;
#include "440vulkan.c"
vulVulkan vul;
#include "450buffer.c"
#include "450image.c"
#include "450swap.c"
swapChain swap;
#include "460pipeline.c"
pipePipeline pipeline;
#include "460shader.c"
#include "480uniform.c"
#include "480description.c"
#include "510texture.c"
#include "470vector.c"
#include "490matrix.c"
#include "490isometry.c"
#include "490camera.c"
#include "470mesh.c"
#include "470mesh2D.c"
#include "470mesh3D.c"
#include "470vesh.c"
#include "520landscape.c"



/*** ARTWORK ******************************************************************/

/* Landscape elevation data and functions to set them. Keep in mind that each of 
our veshes is limited to 65,536 triangles. And the landscape and water will each 
use 2 * SIZE^2 triangles. So don't set SIZE to more than 181. */
#define SIZE 100
float grassData[SIZE * SIZE];
float rockData[SIZE * SIZE];
float waterData[SIZE * SIZE];

/* Randomly generates the elevation data for the grass. */
void setGrass() {
    landFlat(SIZE, grassData, 0.0);
    time_t t;
	srand((unsigned)time(&t));
    for (int i = 0; i < 32; i += 1)
		landFaultRandomly(SIZE, grassData, 1.5 - i * 0.04);
	for (int i = 0; i < 4; i += 1)
		landBlur(SIZE, grassData);
	for (int i = 0; i < 16; i += 1)
		landBump(
		    SIZE, grassData, landInt(0, SIZE - 1), landInt(0, SIZE - 1), 5.0, 
		    2.0);
}

/* Randomly generates the elevation data for the rock. */
void setRock() {
	for (int i = 0; i < SIZE * SIZE; i += 1)
		rockData[i] = grassData[i] + landDouble(-1.1, -0.1);
	for (int i = 0; i < 16; i += 1)
		landBump(
		    SIZE, rockData, landInt(0, SIZE - 1), landInt(0, SIZE - 1), 5.0, 
		    5.0);
}

/* Randomly generates the elevation data for the water. The water is essentially 
a sine wave of amplitude 0.1 and period 5. */
void setWater() {
    float landMin, landMean, landMax;
    landStatistics(SIZE, grassData, &landMin, &landMean, &landMax);
    landFlat(SIZE, waterData, landMean);
    for (int i = 0; i < SIZE; i += 1)
        for (int j = 0; j < SIZE; j += 1)
            waterData[i * SIZE + j] += 0.1 * sin(i * 2.0 * M_PI  / 5.0);
}

/* Given landscape data and (x, y) coordinates with respect to that landscape, 
returns the approximate z coordinate at the surface. */
float elevation(float data[SIZE * SIZE], float x, float y) {
	/* Where have we seen this kind of calculation before now? */
	int flX = (int)floor(x);
	int ceX = (int)ceil(x);
	int flY = (int)floor(y);
	int ceY = (int)ceil(y);
	float frX = x - flX;
	float frY = y - flY;
	float first = (1 - frX) * (1 - frY) * data[flX * SIZE + flY];
	float second = (1 - frX) * (frY) * data[flX * SIZE + ceY];
	float third = (frX) * (1 - frY) * data[ceX * SIZE + flY];
	float fourth = (frX) * (frY) * data[ceX * SIZE + ceY];
	return first + second + third + fourth;
}

/* Three veshes using the attribute style XYZ, ST, NOP. */
veshStyle style;
veshVesh grassVesh, rockVesh, waterVesh, heroVesh;

/* Initializes the veshes. Upon success (return code 0), don't forget to 
finalizeVeshes later. */
int initializeVeshes() {
    meshMesh mesh;
    /* Randomly generate the landscape. */
    setGrass();
    setRock();
    setWater();
    /* Make the hero vesh. */
    if (mesh3DInitializeCapsule(&mesh, 0.5, 2.0, 16, 32) != 0) {
        return 6;
    }
    if (veshInitializeMesh(&heroVesh, &mesh) != 0) {
        meshFinalize(&mesh);
        return 5;
    }
    meshFinalize(&mesh);
    /* Make the land vesh. */
    if (mesh3DInitializeLandscape(&mesh, SIZE, 1.0, grassData) != 0) {
        veshFinalize(&heroVesh);
        return 4;
    }
    if (veshInitializeMesh(&grassVesh, &mesh) != 0) {
        meshFinalize(&mesh);
        veshFinalize(&heroVesh);
        return 3;
    }
    meshFinalize(&mesh);
    /* Make the rock vesh. */
    if (mesh3DInitializeLandscape(&mesh, SIZE, 1.0, rockData) != 0) {
        veshFinalize(&grassVesh);
        veshFinalize(&heroVesh);
        return 2;
    }
    if (veshInitializeMesh(&rockVesh, &mesh) != 0) {
        meshFinalize(&mesh);
        veshFinalize(&grassVesh);
        veshFinalize(&heroVesh);
        return 1;
    }
    meshFinalize(&mesh);
    /* Make the water vesh. */
    if (mesh3DInitializeLandscape(&mesh, SIZE, 1.0, waterData) != 0) {
        veshFinalize(&rockVesh);
        veshFinalize(&grassVesh);
        veshFinalize(&heroVesh);
        return 2;
    }
    if (veshInitializeMesh(&waterVesh, &mesh) != 0) {
        meshFinalize(&mesh);
        veshFinalize(&rockVesh);
        veshFinalize(&grassVesh);
        veshFinalize(&heroVesh);
        return 1;
    }
    meshFinalize(&mesh);
    return 0;
}

void finalizeVeshes() {
    veshFinalize(&waterVesh);
	veshFinalize(&rockVesh);
    veshFinalize(&grassVesh);
    veshFinalize(&heroVesh);
}

/* Three textures. */
#define TEXNUM 4
VkSampler texSampRepeat, texSampClamp;
VkSampler texSamps[TEXNUM];
VkImage texIms[TEXNUM];
VkDeviceMemory texImMems[TEXNUM];
VkImageView texImViews[TEXNUM];

/* Initializes the textures. Upon success (return code 0), don't forget to 
finalizeTextures later. */
int initializeTextures() {
    /* Initialize two samplers. */
    if (texInitializeSampler(
            &texSampRepeat, VK_SAMPLER_ADDRESS_MODE_REPEAT, 
            VK_SAMPLER_ADDRESS_MODE_REPEAT, 16.0) != 0) {
        return 5;
    }
    if (texInitializeSampler(
            &texSampClamp, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 
            VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, 16.0) != 0) {
        texFinalizeSampler(&texSampRepeat);
        return 4;
    }
    /* The first sampler acts on the first three textures. The second sampler 
    acts on the fourth texture. */
    texSamps[0] = texSampRepeat;
    texSamps[1] = texSampRepeat;
    texSamps[2] = texSampRepeat;
    texSamps[3] = texSampClamp;
    /* Initialize four textures. */
    if (texInitializeFile(
            &texIms[0], &texImMems[0], &texImViews[0], "grass.jpg") != 0) {
        texFinalizeSampler(&texSampClamp);
        texFinalizeSampler(&texSampRepeat);
        return 3;
    }
    if (texInitializeFile(
            &texIms[1], &texImMems[1], &texImViews[1], "rock.jpg") != 0) {
        texFinalize(&texIms[0], &texImMems[0], &texImViews[0]);
        texFinalizeSampler(&texSampClamp);
        texFinalizeSampler(&texSampRepeat);
        return 2;
    }
    if (texInitializeFile(
            &texIms[2], &texImMems[2], &texImViews[2], "water.jpg") != 0) {
        texFinalize(&texIms[1], &texImMems[1], &texImViews[1]);
        texFinalize(&texIms[0], &texImMems[0], &texImViews[0]);
        texFinalizeSampler(&texSampClamp);
        texFinalizeSampler(&texSampRepeat);
        return 1;
    }
    if (texInitializeFile(
            &texIms[3], &texImMems[3], &texImViews[3], "hero.jpg") != 0) {
        texFinalize(&texIms[2], &texImMems[2], &texImViews[2]);
        texFinalize(&texIms[1], &texImMems[1], &texImViews[1]);
        texFinalize(&texIms[0], &texImMems[0], &texImViews[0]);
        texFinalizeSampler(&texSampClamp);
        texFinalizeSampler(&texSampRepeat);
        return 1;
    }
    return 0;
}

void finalizeTextures() {
    texFinalize(&texIms[3], &texImMems[3], &texImViews[3]);
    texFinalize(&texIms[2], &texImMems[2], &texImViews[2]);
    texFinalize(&texIms[1], &texImMems[1], &texImViews[1]);
    texFinalize(&texIms[0], &texImMems[0], &texImViews[0]);
    texFinalizeSampler(&texSampClamp);
    texFinalizeSampler(&texSampRepeat);
}

/* Camera and hero data. */
camCamera camera;
float cameraRho = 10.0, cameraPhi = M_PI / 4.0, cameraTheta = M_PI / 4.0;
float heroPos[3] = {0.5 * SIZE, 0.5 * SIZE, 0.0};
float heroHeading = 0.0;
int heroWDown = 0, heroSDown = 0, heroADown = 0, heroDDown = 0;

/* Called by setBodyUniforms. */
void setHero() {
    float changeInTime = gui.currentTime - gui.lastTime;
    if (heroADown)
        heroHeading += M_PI * changeInTime;
    if (heroDDown)
        heroHeading -= M_PI * changeInTime;
    if (heroWDown) {
        heroPos[0] += 2.0 * changeInTime * cos(heroHeading);
        heroPos[1] += 2.0 * changeInTime * sin(heroHeading);
    }
    if (heroSDown) {
        heroPos[0] -= 2.0 * changeInTime * cos(heroHeading);
        heroPos[1] -= 2.0 * changeInTime * sin(heroHeading);
    }
    if (0.0 <= heroPos[0] && heroPos[0] <= SIZE - 1.0 && 
            0.0 <= heroPos[1] && heroPos[1] <= SIZE - 1.0) {
        float grassElev = elevation(grassData, heroPos[0], heroPos[1]);
        float rockElev = elevation(rockData, heroPos[0], heroPos[1]);
        if (grassElev >= rockElev)
        	heroPos[2] = 1.0 + grassElev;
        else
        	heroPos[2] = 1.0 + rockElev;
    }
}

/* Our scene has four bodies: grass, rock, water, hero. */
int bodyNum = 4;

/* Initializes the bodies in the scene. Upon success (return code 0), don't 
forget to finalizeScene later. */
int initializeScene() {
    camSetProjectionType(&camera, camPERSPECTIVE);
    return 0;
}

void finalizeScene() {
    return;
}

shaProgram shaProg;

/* Initializes the artwork. Upon success (return code 0), don't forget to 
finalizeArtwork later. */
int initializeArtwork() {
    if (shaInitialize(&shaProg, "520vert.spv", "520frag.spv") != 0) {
        return 5;
    }
    int attrDims[3] = {3, 2, 3};
    if (veshInitializeStyle(&style, 3, attrDims) != 0) {
        shaFinalize(&shaProg);
        return 4;
    }
    if (initializeVeshes() != 0) {
        veshFinalizeStyle(&style);
        shaFinalize(&shaProg);
        return 3;
    }
    if (initializeTextures() != 0) {
        finalizeVeshes();
        veshFinalizeStyle(&style);
        shaFinalize(&shaProg);
        return 2;
    }
    if (initializeScene() != 0) {
        finalizeTextures();
        finalizeVeshes();
        veshFinalizeStyle(&style);
        shaFinalize(&shaProg);
        return 1;
    }
    return 0;
}

void finalizeArtwork() {
    finalizeScene();
    finalizeTextures();
    finalizeVeshes();
    veshFinalizeStyle(&style);
    shaFinalize(&shaProg);
}



/*** UNIFORMS *****************************************************************/

typedef struct SceneUniforms SceneUniforms;
struct SceneUniforms {
    float cameraT[4][4];
};

VkBuffer *sceneUnifBufs;
VkDeviceMemory *sceneUnifBufsMemory;
void **sceneUnifBufsMapped;

void setSceneUniforms(uint32_t index) {
    SceneUniforms sceneUnifs;
    camSetFrustum(
        &camera, M_PI / 6.0, cameraRho, 10.0, swap.extent.width, 
        swap.extent.height);
    camLookAt(&camera, heroPos, cameraRho, cameraPhi, cameraTheta);
    float cam[4][4];
    camGetProjectionInverseIsometry(&camera, cam);
    mat4Transpose(cam, sceneUnifs.cameraT);
	memcpy(sceneUnifBufsMapped[index], &sceneUnifs, sizeof(SceneUniforms));
}

typedef struct BodyUniforms BodyUniforms;
struct BodyUniforms {
    float modelingT[4][4];
    uint32_t texIndices[4];
};

VkBuffer *bodyUnifBufs;
VkDeviceMemory *bodyUnifBufsMemory;
void **bodyUnifBufsMapped;
unifAligned aligned;

void setBodyUniforms(uint32_t index) {
    float isometry[4][4] = {
        {1.0, 0.0, 0.0, 0.0},                           // row 0, not column 0
        {0.0, 1.0, 0.0, 0.0},                           // row 1
        {0.0, 0.0, 1.0, 0.0},                           // row 2
        {0.0, 0.0, 0.0, 1.0}};                          // row 3
    /* The grass uses a trivial isometry and the first texture. */
    BodyUniforms *bodyUnifs = (BodyUniforms *)unifGetAligned(&aligned, 0);
    mat4Transpose(isometry, bodyUnifs->modelingT);
    bodyUnifs->texIndices[0] = 0;
    /* The rock uses a trivial isometry and the second texture. */
    bodyUnifs = (BodyUniforms *)unifGetAligned(&aligned, 1);
    mat4Transpose(isometry, bodyUnifs->modelingT);
    bodyUnifs->texIndices[0] = 1;
    /* The water uses a translating isometry and the third texture. */
    bodyUnifs = (BodyUniforms *)unifGetAligned(&aligned, 2);
    float t = gui.currentTime - gui.startTime;
    isometry[0][3] = fmod(t, 5.0);
    mat4Transpose(isometry, bodyUnifs->modelingT);
    bodyUnifs->texIndices[0] = 2;
    /* The hero has a heading and a location and uses the fourth texture. */
    setHero();
    bodyUnifs = (BodyUniforms *)unifGetAligned(&aligned, 3);
    isoIsometry isom;
    float axis[3] = {0.0, 0.0, 1.0};
    float rot[3][3];
    mat3AngleAxisRotation(heroHeading, axis, rot);
    isoSetRotation(&isom, rot);
    isoSetTranslation(&isom, heroPos);
    isoGetHomogeneous(&isom, isometry);
    mat4Transpose(isometry, bodyUnifs->modelingT);
    bodyUnifs->texIndices[0] = 3;
    /* Copy the body UBO bits from the CPU to the GPU. */
    int amount = aligned.uboNum * aligned.alignedSize;
    memcpy(bodyUnifBufsMapped[index], aligned.data, amount);
}

#define UNIFSCENE 0
#define UNIFBODY 1
#define UNIFTEX 2
#define UNIFNUM 3
int descriptorCounts[UNIFNUM] = {1, 1, TEXNUM};
VkDescriptorType descriptorTypes[UNIFNUM] = {
    VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 
    VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 
    VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER};
VkShaderStageFlags descriptorStageFlagss[UNIFNUM] = {
    VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 
    VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 
    VK_SHADER_STAGE_FRAGMENT_BIT};
int descriptorBindings[UNIFNUM] = {0, 1, 2};
descDescription desc;

/* Plug-in function for descInitialize. Provides the parts of the customization 
that are difficult to abstract. */
void setDescriptorSet(descDescription *desc, int index) {
    /* Update the descriptor for the scene UBO. */
    VkDescriptorBufferInfo sceneUBOInfo = {0};
    sceneUBOInfo.buffer = sceneUnifBufs[index];
    sceneUBOInfo.offset = 0;
    sceneUBOInfo.range = sizeof(SceneUniforms);
    VkDescriptorBufferInfo sceneUBODescBufInfos[1] = {sceneUBOInfo};
    VkWriteDescriptorSet sceneUBOWrite = {0};
    sceneUBOWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    sceneUBOWrite.dstSet = desc->descriptorSets[index];
    sceneUBOWrite.dstBinding = descriptorBindings[UNIFSCENE];
    sceneUBOWrite.dstArrayElement = 0;
    sceneUBOWrite.descriptorType = descriptorTypes[UNIFSCENE];
    sceneUBOWrite.descriptorCount = descriptorCounts[UNIFSCENE];
    sceneUBOWrite.pBufferInfo = sceneUBODescBufInfos;
    /* Update the descriptor for the body UBO. */
    VkDescriptorBufferInfo bodyUBOInfo = {0};
    bodyUBOInfo.buffer = bodyUnifBufs[index];
    bodyUBOInfo.offset = 0;
    bodyUBOInfo.range = unifAlignment(sizeof(BodyUniforms));
    VkDescriptorBufferInfo bodyUBODescBufInfos[1] = {bodyUBOInfo};
    VkWriteDescriptorSet bodyUBOWrite = {0};
    bodyUBOWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    bodyUBOWrite.dstSet = desc->descriptorSets[index];
    bodyUBOWrite.dstBinding = descriptorBindings[UNIFBODY];
    bodyUBOWrite.dstArrayElement = 0;
    bodyUBOWrite.descriptorCount = descriptorCounts[UNIFBODY];
    bodyUBOWrite.descriptorType = descriptorTypes[UNIFBODY];
    bodyUBOWrite.pBufferInfo = bodyUBODescBufInfos;
    /* Update texNum descriptors for the texture array. */
    VkDescriptorImageInfo descriptorImageInfos[TEXNUM];
    for (int i = 0; i < TEXNUM; i += 1) {
        VkDescriptorImageInfo imageInfo = {0};
        imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        imageInfo.imageView = texImViews[i];
        imageInfo.sampler = texSamps[i];
        descriptorImageInfos[i] = imageInfo;
    }
    VkWriteDescriptorSet samplerWrite = {0};
    samplerWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    samplerWrite.dstSet = desc->descriptorSets[index];
    samplerWrite.dstBinding = descriptorBindings[UNIFTEX];
    samplerWrite.dstArrayElement = 0;
    samplerWrite.descriptorType = descriptorTypes[UNIFTEX];
    samplerWrite.descriptorCount = descriptorCounts[UNIFTEX];
    samplerWrite.pImageInfo = descriptorImageInfos;
    /* Finally, actually update the three descriptors. */
    VkWriteDescriptorSet descWrites[3] = {
        sceneUBOWrite, bodyUBOWrite, samplerWrite};
    vkUpdateDescriptorSets(vul.device, 3, descWrites, 0, NULL);
}

/* Initializes the machinery for communicating uniforms to shaders. On success 
(return code 0), don't forget to finalizeUniforms when you're done. */
int initializeUniforms() {
    if (unifInitializeBuffers(
            &sceneUnifBufs, &sceneUnifBufsMemory, &sceneUnifBufsMapped, 
            sizeof(SceneUniforms)) != 0)
        return 4;
    if (unifInitializeBuffers(
            &bodyUnifBufs, &bodyUnifBufsMemory, &bodyUnifBufsMapped, 
            bodyNum * unifAlignment(sizeof(BodyUniforms))) != 0) {
        unifFinalizeBuffers(
        	&sceneUnifBufs, &sceneUnifBufsMemory, &sceneUnifBufsMapped);
        return 3;
    }
    if (unifInitializeAligned(&aligned, bodyNum, sizeof(BodyUniforms)) != 0) {
        unifFinalizeBuffers(
        	&bodyUnifBufs, &bodyUnifBufsMemory, &bodyUnifBufsMapped);
        unifFinalizeBuffers(
        	&sceneUnifBufs, &sceneUnifBufsMemory, &sceneUnifBufsMapped);
        return 2;
    }
    if (descInitialize(
            &desc, UNIFNUM, descriptorCounts, descriptorTypes, 
            descriptorStageFlagss, descriptorBindings, setDescriptorSet) != 0) {
        unifFinalizeAligned(&aligned);
        unifFinalizeBuffers(
        	&bodyUnifBufs, &bodyUnifBufsMemory, &bodyUnifBufsMapped);
        unifFinalizeBuffers(
        	&sceneUnifBufs, &sceneUnifBufsMemory, &sceneUnifBufsMapped);
        return 1;
    }
    return 0;
}

void finalizeUniforms() {
    descFinalize(&desc);
    unifFinalizeAligned(&aligned);
    unifFinalizeBuffers(
    	&bodyUnifBufs, &bodyUnifBufsMemory, &bodyUnifBufsMapped);
    unifFinalizeBuffers(
    	&sceneUnifBufs, &sceneUnifBufsMemory, &sceneUnifBufsMapped);
}



/*** MAIN *********************************************************************/

/* Called by presentFrame. Records specific rendering commands --- which clear 
color to use, which meshes to render, etc. --- into a command buffer. */
int recordCommandBuffer(VkCommandBuffer cmdBuf, uint32_t index) {
    /* Begin command buffer. */
	VkCommandBufferBeginInfo beginInfo = {0};
    beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
	if (vkBeginCommandBuffer(cmdBuf, &beginInfo) != VK_SUCCESS) {
		fprintf(stderr, "error: recordCommandBuffer: ");
		fprintf(stderr, "vkBeginCommandBuffer failed\n");
		return 2;
	}
    /* Render pass info. */
	VkRenderPassBeginInfo renderPassInfo = {0};
	renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
	renderPassInfo.renderPass = swap.renderPass;
	renderPassInfo.framebuffer = swap.framebuffers[index];
    renderPassInfo.renderArea.offset.x = 0;
	renderPassInfo.renderArea.offset.y = 0;
	renderPassInfo.renderArea.extent = swap.extent;
	/* Render pass clear color and depth. */
	VkClearValue clearColor = {0.0, 0.0, 0.0, 1.0};
	VkClearValue clearDepth = {1.0, 0.0};
	VkClearValue clearValues[2] = {clearColor, clearDepth};
	renderPassInfo.clearValueCount = 2;
	renderPassInfo.pClearValues = clearValues;
    /* Begin render pass. */
	vkCmdBeginRenderPass(cmdBuf, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
    vkCmdBindPipeline(
		cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.graphicsPipeline);
	/* Render a vesh with a body UBO. */
	uint32_t bodyUBOOffset = 0;
	vkCmdBindDescriptorSets(
		cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipelineLayout, 0, 1, 
		&(desc.descriptorSets[swap.curFrame]), 1, &bodyUBOOffset);
	veshRender(&grassVesh, cmdBuf);
	/* Render another vesh with another body UBO. */
	bodyUBOOffset += unifAlignment(sizeof(BodyUniforms));
	vkCmdBindDescriptorSets(
		cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipelineLayout, 0, 1, 
		&(desc.descriptorSets[swap.curFrame]), 1, &bodyUBOOffset);
	veshRender(&rockVesh, cmdBuf);
	/* Render another vesh with another body UBO. */
	bodyUBOOffset += unifAlignment(sizeof(BodyUniforms));
	vkCmdBindDescriptorSets(
		cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipelineLayout, 0, 1, 
		&(desc.descriptorSets[swap.curFrame]), 1, &bodyUBOOffset);
	veshRender(&waterVesh, cmdBuf);
	/* Render another vesh with another body UBO. */
	bodyUBOOffset += unifAlignment(sizeof(BodyUniforms));
	vkCmdBindDescriptorSets(
		cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipelineLayout, 0, 1, 
		&(desc.descriptorSets[swap.curFrame]), 1, &bodyUBOOffset);
	veshRender(&heroVesh, cmdBuf);
    /* End render pass and command buffer. */
	vkCmdEndRenderPass(cmdBuf);
	if (vkEndCommandBuffer(cmdBuf) != VK_SUCCESS) {
		fprintf(stderr, "error: recordCommandBuffer: ");
		fprintf(stderr, "vkEndCommandBuffer failed\n");
		return 1;
	}
    return 0;
}

/* Called by presentFrame. Returns an error code (0 on success). On success, 
remember to call the appropriate finalizers when you're done. */
int finalizeInitializeSwapChainPipeline() {
    int width = 0, height = 0;
    glfwGetFramebufferSize(gui.window, &width, &height);
    while (width == 0 || height == 0) {
        glfwGetFramebufferSize(gui.window, &width, &height);
        glfwWaitEvents();
    }
    vkDeviceWaitIdle(vul.device);
    pipeFinalize(&pipeline);
    swapFinalize(&swap);
    if (swapInitialize(&swap) != 0)
        return 2;
    if (pipeInitialize(
            &pipeline, shaProg.shaderStages, &(style.vertexInputInfo), 
            &(style.inputAssembly), 1, &(desc.descriptorSetLayout), 0, 
            NULL) != 0) {
		swapFinalize(&swap);
        return 1;
    }
    return 0;
}

/* Plug-in function for guiRun. Presents one frame to the window. */
int presentFrame() {
    /* Wait until the previous frame has finished. */
    vkWaitForFences(
    	vul.device, 1, &(swap.inFlightFences[swap.curFrame]), VK_TRUE, 
    	UINT64_MAX);
	/* Figure out which element of the swap chain to operate on, and whether the 
    swap chain has become incompatible with the surface (which usually happens 
    because of resizing the window). */
    uint32_t index;
    VkResult result = vkAcquireNextImageKHR(
        vul.device, swap.swapChain, UINT64_MAX, 
        swap.imageAvailSems[swap.curFrame], VK_NULL_HANDLE, &index);
    if (result == VK_ERROR_OUT_OF_DATE_KHR) {
    	/* Being out of date is not in itself a fatal error. But we can't 
    	present this frame anyway. See Overvoorde (2023, page 142). */
        if (finalizeInitializeSwapChainPipeline() != 0)
        	return 5;
    } else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
        fprintf(stderr, "error: presentFrame: ");
        fprintf(stderr, "vkAcquireNextImageKHR returned weird value\n");
        return 4;
    }
	vkResetFences(vul.device, 1, &(swap.inFlightFences[swap.curFrame]));
    /* Send data to the body UBOs as well as the scene UBO. */
    setSceneUniforms(swap.curFrame);
    setBodyUniforms(swap.curFrame);
    /* Prepare the command buffer, so that it can be submitted to Vulkan. */
    vkResetCommandBuffer(vul.commandBuffers[swap.curFrame], 0);
    recordCommandBuffer(vul.commandBuffers[swap.curFrame], index);
    VkSubmitInfo submitInfo = {0};
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    VkPipelineStageFlags waitStages[] = {
        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
    submitInfo.pWaitDstStageMask = waitStages;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &(vul.commandBuffers[swap.curFrame]);
    VkSemaphore waitSemaphores[1] = {swap.imageAvailSems[swap.curFrame]};
    submitInfo.waitSemaphoreCount = 1;
    submitInfo.pWaitSemaphores = waitSemaphores;
    VkSemaphore signalSemaphores[1] = {swap.renderDoneSems[swap.curFrame]};
    submitInfo.signalSemaphoreCount = 1;
    submitInfo.pSignalSemaphores = signalSemaphores;
    /* Submit the command buffer to Vulkan, to render the frame. */
    if (vkQueueSubmit(
            vul.graphicsQueue, 1, &submitInfo, 
            swap.inFlightFences[swap.curFrame]) != VK_SUCCESS) {
        fprintf(stderr, "error: presentFrame: vkQueueSubmit failed\n");
        return 3;
    }
    /* Prepare to present the frame to the user. */
    VkPresentInfoKHR presentInfo = {0};
    presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
    presentInfo.waitSemaphoreCount = 1;
    presentInfo.pWaitSemaphores = signalSemaphores;
    VkSwapchainKHR swapChains[] = {swap.swapChain};
    presentInfo.swapchainCount = 1;
    presentInfo.pSwapchains = swapChains;
    presentInfo.pImageIndices = &index;
    /* Present the frame, reinitializing the swap chain if necessary. */
    result = vkQueuePresentKHR(vul.presentQueue, &presentInfo);
    if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || 
            gui.framebufferResized) {
        guiSetFramebufferResized(&gui, 0);
        if (finalizeInitializeSwapChainPipeline() != 0)
            return 2;
    } else if (result != VK_SUCCESS) {
        fprintf(stderr, "error: presentFrame: ");
        fprintf(stderr, "vkQueuePresentKHR returned weird value\n");
        return 1;
    }
    /* We're finally done with this frame. */
    swapIncrementFrame(&swap);
    return 0;
}

void handleKey(
		GLFWwindow *window, int key, int scancode, int action, int mods) {
    /* Detect which modifier keys are down. */
    int shiftIsDown, controlIsDown, altOptionIsDown, superCommandIsDown;
    shiftIsDown = mods & GLFW_MOD_SHIFT;
    controlIsDown = mods & GLFW_MOD_CONTROL;
    altOptionIsDown = mods & GLFW_MOD_ALT;
    superCommandIsDown = mods & GLFW_MOD_SUPER;
    /* Handle the camera. */
    if (key == GLFW_KEY_P && action == GLFW_PRESS) {
        if (camera.projectionType == camORTHOGRAPHIC)
            camSetProjectionType(&camera, camPERSPECTIVE);
        else
            camSetProjectionType(&camera, camORTHOGRAPHIC);
    } else if (key == GLFW_KEY_J)
        cameraTheta -= M_PI / 36.0;
    else if (key == GLFW_KEY_L)
        cameraTheta += M_PI / 36.0;
    else if (key == GLFW_KEY_I)
        cameraPhi -= M_PI / 36.0;
    else if (key == GLFW_KEY_K)
        cameraPhi += M_PI / 36.0;
    else if (key == GLFW_KEY_O)
        cameraRho *= 0.95;
    else if (key == GLFW_KEY_U)
        cameraRho *= 1.05;
    /* Update which hero keys are down. They affect the hero automatically on 
    each time step. */
    if (key == GLFW_KEY_W) {
        if (action == GLFW_PRESS)
            heroWDown = 1;
        else if (action == GLFW_RELEASE)
            heroWDown = 0;
    } if (key == GLFW_KEY_S) {
        if (action == GLFW_PRESS)
            heroSDown = 1;
        else if (action == GLFW_RELEASE)
            heroSDown = 0;
    } if (key == GLFW_KEY_A) {
        if (action == GLFW_PRESS)
            heroADown = 1;
        else if (action == GLFW_RELEASE)
            heroADown = 0;
    } if (key == GLFW_KEY_D) {
        if (action == GLFW_PRESS)
            heroDDown = 1;
        else if (action == GLFW_RELEASE)
            heroDDown = 0;
    }
}

int main() {
    if (guiInitialize(&gui, 512, 512, "Vulkan") != 0)
        return 6;
    if (vulInitialize(&vul) != 0) {
        guiFinalize(&gui);
        return 5;
    }
    if (initializeArtwork() != 0) {
		vulFinalize(&vul);
		guiFinalize(&gui);
        return 4;
    }
    if (initializeUniforms() != 0) {
		finalizeArtwork();
		vulFinalize(&vul);
		guiFinalize(&gui);
        return 3;
    }
    if (swapInitialize(&swap) != 0) {
		finalizeUniforms();
		finalizeArtwork();
		vulFinalize(&vul);
		guiFinalize(&gui);
        return 2;
    }
    if (pipeInitialize(
            &pipeline, shaProg.shaderStages, &(style.vertexInputInfo), 
            &(style.inputAssembly), 1, &(desc.descriptorSetLayout), 0, 
            NULL) != 0) {
		swapFinalize(&swap);
		finalizeUniforms();
		finalizeArtwork();
		vulFinalize(&vul);
		guiFinalize(&gui);
        return 1;
    }
    guiSetFramePresenter(&gui, presentFrame);
    glfwSetKeyCallback(gui.window, handleKey);
    guiRun(&gui);
    vkDeviceWaitIdle(vul.device);
    pipeFinalize(&pipeline);
    swapFinalize(&swap);
    finalizeUniforms();
    finalizeArtwork();
    vulFinalize(&vul);
    guiFinalize(&gui);
    return 0;
}


