70 WaterWindow(
const std::string &path,
const std::string &title,
int width,
int height,
bool fullscreen,
bool enable_vsync) :
mxvk::
VK_Window(title, width, height, fullscreen,
MXVK_VALIDATION, enable_vsync), shader_root((path.empty() ? std::string(WATER_ASSET_DIR) : path) +
"/data") {
setClearColor(0.60f, 0.78f, 0.96f, 1.0f); }
73 if (
device != VK_NULL_HANDLE) {
76 destroyPipeline(sky_pipeline);
77 destroyPipeline(water_pipeline);
78 destroyMesh(water_mesh);
81 void event(SDL_Event &e)
override {
82 if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) {
87 if (e.type == SDL_EVENT_KEY_DOWN || e.type == SDL_EVENT_KEY_UP) {
88 const bool pressed = e.type == SDL_EVENT_KEY_DOWN;
91 rotate_left = pressed;
94 rotate_right = pressed;
109 if (e.type == SDL_EVENT_QUIT) {
115 destroyPipeline(sky_pipeline);
116 destroyPipeline(water_pipeline);
120 if (water_mesh_uploaded) {
124 uploadMesh(generateWaterMesh(), water_mesh);
125 water_mesh_uploaded =
true;
129 if (!water_mesh_uploaded || !ensurePipelines()) {
133 const auto now = std::chrono::steady_clock::now();
134 const float delta_seconds = std::chrono::duration<float>(now - last_frame_time).count();
135 last_frame_time = now;
136 updateCamera(delta_seconds);
138 const float elapsed_seconds = std::chrono::duration<float>(now - start_time).count();
140 const float aspect = (extent.height > 0U) ?
static_cast<float>(extent.width) /
static_cast<float>(extent.height) : 1.0f;
142 const glm::vec3 camera_target(0.0f, -0.15f, -18.0f);
143 const float yaw = glm::radians(camera_yaw_degrees);
144 const float pitch = glm::radians(camera_pitch_degrees);
145 const glm::vec3 camera_offset(std::sin(yaw) * std::cos(pitch) * camera_distance, std::sin(pitch) * camera_distance, std::cos(yaw) * std::cos(pitch) * camera_distance);
146 const glm::vec3 camera_pos = camera_target + camera_offset;
147 const glm::mat4 view = glm::lookAt(camera_pos, camera_target, glm::vec3(0.0f, 1.0f, 0.0f));
148 glm::mat4 projection = glm::perspective(glm::radians(62.0f), aspect, 0.1f, 260.0f);
149 projection[1][1] *= -1.0f;
151 const PushConstants push_constants{
153 glm::vec4(camera_pos, elapsed_seconds),
154 glm::vec4(aspect, SCENE_REFERENCE_ASPECT, 0.0f, 0.0f),
157 drawSky(cmd, sky_pipeline, push_constants);
158 drawMesh(cmd, water_mesh, water_pipeline, push_constants);
162 std::string shader_root;
163 std::chrono::steady_clock::time_point start_time{std::chrono::steady_clock::now()};
164 std::chrono::steady_clock::time_point last_frame_time{start_time};
165 MeshResources water_mesh;
166 PipelineResources water_pipeline;
167 PipelineResources sky_pipeline;
168 bool water_mesh_uploaded =
false;
169 float camera_yaw_degrees = 0.0f;
170 float camera_pitch_degrees = 10.5f;
171 float camera_distance = 27.5f;
172 bool rotate_left =
false;
173 bool rotate_right =
false;
174 bool zoom_in =
false;
175 bool zoom_out =
false;
177 void updateCamera(
float delta_seconds) {
178 constexpr float ROTATION_SPEED_DEGREES = 42.0f;
182 camera_yaw_degrees -= ROTATION_SPEED_DEGREES * delta_seconds;
185 camera_yaw_degrees += ROTATION_SPEED_DEGREES * delta_seconds;
188 camera_distance -=
ZOOM_SPEED * delta_seconds;
191 camera_distance +=
ZOOM_SPEED * delta_seconds;
194 camera_pitch_degrees = std::clamp(camera_pitch_degrees, -4.0f, 46.0f);
195 camera_distance = std::clamp(camera_distance, 10.0f, 72.0f);
198 static MeshData generateWaterMesh() {
200 mesh.vertices.reserve(
static_cast<std::size_t
>(WATER_GRID_RESOLUTION + 1) *
static_cast<std::size_t
>(WATER_GRID_RESOLUTION + 1));
201 mesh.indices.reserve(
static_cast<std::size_t
>(WATER_GRID_RESOLUTION) *
static_cast<std::size_t
>(WATER_GRID_RESOLUTION) * 6U);
204 const float vz =
static_cast<float>(z) /
static_cast<float>(WATER_GRID_RESOLUTION);
206 const float vx =
static_cast<float>(x) /
static_cast<float>(WATER_GRID_RESOLUTION);
207 mesh.vertices.push_back({
208 glm::vec3((vx - 0.5f) * WATER_SIZE, 0.0f, (vz - 0.5f) * WATER_SIZE),
209 glm::vec2(vx * 72.0f, vz * 72.0f),
210 glm::vec3(0.0f, 1.0f, 0.0f),
211 glm::vec4(0.30f, 0.72f, 0.92f, 1.0f),
216 const auto vertex_index = [](
int x,
int z) {
return static_cast<std::uint32_t
>(z * (
WATER_GRID_RESOLUTION + 1) + x); };
219 const std::uint32_t a = vertex_index(x, z);
220 const std::uint32_t b = vertex_index(x + 1, z);
221 const std::uint32_t c = vertex_index(x, z + 1);
222 const std::uint32_t d = vertex_index(x + 1, z + 1);
223 mesh.indices.insert(mesh.indices.end(), {a, c, b, b, c, d});
230 void uploadMesh(
const MeshData &data, MeshResources &mesh)
const {
231 const mxvk::VulkanContext
context{
237 mesh.indexCount =
static_cast<uint32_t
>(data.indices.size());
239 const VkDeviceSize vertex_size =
sizeof(SceneVertex) * data.vertices.size();
240 uploadDeviceBuffer(
context, data.vertices.data(), vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, mesh.vertexBuffer);
242 const VkDeviceSize index_size =
sizeof(std::uint32_t) * data.indices.size();
243 uploadDeviceBuffer(
context, data.indices.data(), index_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT, mesh.indexBuffer);
246 void drawMesh(VkCommandBuffer cmd,
const MeshResources &mesh,
const PipelineResources &pipeline,
const PushConstants &push_constants)
const {
247 if (mesh.vertexBuffer.buffer == VK_NULL_HANDLE || mesh.indexBuffer.buffer == VK_NULL_HANDLE || pipeline.pipeline == VK_NULL_HANDLE) {
251 const VkDeviceSize offsets[] = {0};
252 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
253 vkCmdBindVertexBuffers(cmd, 0, 1, &mesh.vertexBuffer.buffer, offsets);
254 vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
255 vkCmdPushConstants(cmd, pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(PushConstants), &push_constants);
256 vkCmdDrawIndexed(cmd, mesh.indexCount, 1, 0, 0, 0);
259 void drawSky(VkCommandBuffer cmd,
const PipelineResources &pipeline,
const PushConstants &push_constants)
const {
260 if (pipeline.pipeline == VK_NULL_HANDLE) {
264 vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
265 vkCmdPushConstants(cmd, pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(PushConstants), &push_constants);
266 vkCmdDraw(cmd, 3, 1, 0, 0);
269 void uploadDeviceBuffer(
const mxvk::VulkanContext &
context,
const void *data, VkDeviceSize size, VkBufferUsageFlags usage, mxvk::BufferResource &buffer)
const {
270 mxvk::BufferResource staging;
271 mxvk::create_buffer(
context, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, staging);
275 std::memcpy(staging.
mapped, data,
static_cast<std::size_t
>(size));
288 void destroyMesh(MeshResources &mesh)
const {
294 bool ensurePipelines() {
295 if (sky_pipeline.pipeline == VK_NULL_HANDLE) {
296 createPipeline(
"sky.vert.spv",
"sky.frag.spv",
false,
false,
false, sky_pipeline);
298 if (water_pipeline.pipeline == VK_NULL_HANDLE) {
299 createPipeline(
"water.vert.spv",
"water.frag.spv",
true,
false,
true, water_pipeline);
301 return sky_pipeline.pipeline != VK_NULL_HANDLE && water_pipeline.pipeline != VK_NULL_HANDLE;
304 void createPipeline(
const std::string &vertex_shader,
const std::string &fragment_shader,
bool useVertexInput,
bool alphaBlend,
bool depthTest, PipelineResources &resources) {
309 const std::vector<char> vert_bytes =
loadSpv(shader_root +
"/" + vertex_shader);
310 const std::vector<char> frag_bytes =
loadSpv(shader_root +
"/" + fragment_shader);
313 VkShaderModule frag_module = VK_NULL_HANDLE;
317 std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{};
318 shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
319 shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
320 shader_stages[0].module = vert_module;
321 shader_stages[0].pName =
"main";
322 shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
323 shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
324 shader_stages[1].module = frag_module;
325 shader_stages[1].pName =
"main";
327 VkVertexInputBindingDescription binding_description{};
328 std::array<VkVertexInputAttributeDescription, 4> attribute_descriptions{};
329 if (useVertexInput) {
330 binding_description = vertexBindingDescription();
331 attribute_descriptions = vertexAttributeDescriptions();
334 VkPipelineVertexInputStateCreateInfo vertex_input{};
335 vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
336 vertex_input.vertexBindingDescriptionCount = useVertexInput ? 1U : 0U;
337 vertex_input.pVertexBindingDescriptions = useVertexInput ? &binding_description :
nullptr;
338 vertex_input.vertexAttributeDescriptionCount = useVertexInput ?
static_cast<uint32_t
>(attribute_descriptions.size()) : 0U;
339 vertex_input.pVertexAttributeDescriptions = useVertexInput ? attribute_descriptions.data() :
nullptr;
341 VkPipelineInputAssemblyStateCreateInfo input_assembly{};
342 input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
343 input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
345 VkPipelineViewportStateCreateInfo viewport_state{};
346 viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
347 viewport_state.viewportCount = 1;
348 viewport_state.scissorCount = 1;
350 const VkDynamicState dynamic_states[] = {
351 VK_DYNAMIC_STATE_VIEWPORT,
352 VK_DYNAMIC_STATE_SCISSOR,
354 VkPipelineDynamicStateCreateInfo dynamic_state{};
355 dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
356 dynamic_state.dynamicStateCount = 2;
357 dynamic_state.pDynamicStates = dynamic_states;
359 VkPipelineRasterizationStateCreateInfo rasterizer{};
360 rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
361 rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
362 rasterizer.lineWidth = 1.0f;
363 rasterizer.cullMode = VK_CULL_MODE_NONE;
364 rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
366 VkPipelineMultisampleStateCreateInfo multisampling{};
367 multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
368 multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
370 VkPipelineDepthStencilStateCreateInfo depth_stencil{};
371 depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
372 depth_stencil.depthTestEnable = depthTest ? VK_TRUE : VK_FALSE;
373 depth_stencil.depthWriteEnable = (depthTest && !alphaBlend) ? VK_TRUE : VK_FALSE;
374 depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
376 VkPipelineColorBlendAttachmentState color_blend_attachment{};
377 color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
378 color_blend_attachment.blendEnable = alphaBlend ? VK_TRUE : VK_FALSE;
379 color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
380 color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
381 color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
382 color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
383 color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
384 color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
386 VkPipelineColorBlendStateCreateInfo color_blending{};
387 color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
388 color_blending.attachmentCount = 1;
389 color_blending.pAttachments = &color_blend_attachment;
391 VkPushConstantRange push_constant_range{};
392 push_constant_range.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
393 push_constant_range.size =
sizeof(PushConstants);
395 VkPipelineLayoutCreateInfo pipeline_layout_info{};
396 pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
397 pipeline_layout_info.pushConstantRangeCount = 1;
398 pipeline_layout_info.pPushConstantRanges = &push_constant_range;
399 check_vk(vkCreatePipelineLayout(
device, &pipeline_layout_info,
nullptr, &resources.layout),
"water: failed to create pipeline layout");
401 VkPipelineRenderingCreateInfo pipeline_rendering_info{};
402 pipeline_rendering_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
403 pipeline_rendering_info.colorAttachmentCount = 1;
406 pipeline_rendering_info.depthAttachmentFormat =
depth_format;
409 VkGraphicsPipelineCreateInfo pipeline_info{};
410 pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
411 pipeline_info.pNext = &pipeline_rendering_info;
412 pipeline_info.stageCount =
static_cast<uint32_t
>(shader_stages.size());
413 pipeline_info.pStages = shader_stages.data();
414 pipeline_info.pVertexInputState = &vertex_input;
415 pipeline_info.pInputAssemblyState = &input_assembly;
416 pipeline_info.pViewportState = &viewport_state;
417 pipeline_info.pRasterizationState = &rasterizer;
418 pipeline_info.pMultisampleState = &multisampling;
419 pipeline_info.pDepthStencilState = &depth_stencil;
420 pipeline_info.pColorBlendState = &color_blending;
421 pipeline_info.pDynamicState = &dynamic_state;
422 pipeline_info.layout = resources.layout;
423 pipeline_info.renderPass = VK_NULL_HANDLE;
425 check_vk(vkCreateGraphicsPipelines(
device,
pipeline_cache, 1, &pipeline_info,
nullptr, &resources.pipeline),
"water: failed to create graphics pipeline");
427 destroyPipeline(resources);
428 if (frag_module != VK_NULL_HANDLE) {
429 vkDestroyShaderModule(
device, frag_module,
nullptr);
431 vkDestroyShaderModule(
device, vert_module,
nullptr);
435 vkDestroyShaderModule(
device, frag_module,
nullptr);
436 vkDestroyShaderModule(
device, vert_module,
nullptr);
439 static VkVertexInputBindingDescription vertexBindingDescription() {
440 VkVertexInputBindingDescription binding{};
442 binding.stride =
sizeof(SceneVertex);
443 binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
447 static std::array<VkVertexInputAttributeDescription, 4> vertexAttributeDescriptions() {
448 std::array<VkVertexInputAttributeDescription, 4> attributes{};
449 attributes[0].binding = 0;
450 attributes[0].location = 0;
451 attributes[0].format = VK_FORMAT_R32G32B32_SFLOAT;
452 attributes[0].offset = offsetof(SceneVertex, position);
453 attributes[1].binding = 0;
454 attributes[1].location = 1;
455 attributes[1].format = VK_FORMAT_R32G32_SFLOAT;
456 attributes[1].offset = offsetof(SceneVertex, texCoord);
457 attributes[2].binding = 0;
458 attributes[2].location = 2;
459 attributes[2].format = VK_FORMAT_R32G32B32_SFLOAT;
460 attributes[2].offset = offsetof(SceneVertex, normal);
461 attributes[3].binding = 0;
462 attributes[3].location = 3;
463 attributes[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
464 attributes[3].offset = offsetof(SceneVertex, color);
468 void destroyPipeline(PipelineResources &resources)
const {
469 if (
device == VK_NULL_HANDLE) {
470 resources.pipeline = VK_NULL_HANDLE;
471 resources.layout = VK_NULL_HANDLE;
475 if (resources.pipeline != VK_NULL_HANDLE) {
476 vkDestroyPipeline(
device, resources.pipeline,
nullptr);
477 resources.pipeline = VK_NULL_HANDLE;
479 if (resources.layout != VK_NULL_HANDLE) {
480 vkDestroyPipelineLayout(
device, resources.layout,
nullptr);
481 resources.layout = VK_NULL_HANDLE;