17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-08 23:07:38 +01:00

Vulkan: scene rendering (D1–D7) on a scene walk shared with GL (N6a, N6b)

This commit is contained in:
2026-10-24 20:10:24 +02:00
parent bd589f272d
commit 9b7d02e5fb
107 changed files with 14511 additions and 3924 deletions

View File

@@ -12,9 +12,9 @@ http://mozilla.org/MPL/2.0/.
// same values for the SPIR-V binaries, which glslc compiles without the renderer (the renderer checks they are the same).
//
// the descriptor sets (decision K6 of the plan): set 0 holds the resources of a pass, made when the renderer starts and bound
// with a dynamic offset in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
// the material and what changes between the draws. a layout can have only one set pushed, so the passes without a set of
// their own (the user interface, the compute passes) push set 0
// with dynamic offsets in the ring of the frame; set 1 is pushed with the draws (vkCmdPushDescriptorSet2): the textures of
// the material. a layout can have only one set pushed, so the passes without a set of their own (the user interface, the
// compute passes, the post-processing) push set 0
#pragma once
@@ -28,27 +28,63 @@ namespace vk14::bindings
constexpr std::uint32_t UI_SET = 0;
constexpr std::uint32_t UI_TEXTURE = 0;
// the sets of the passes of the scene (from the stage D1 of the plan)
// the sets of the passes of the scene
constexpr std::uint32_t PASS_SET = 0;
constexpr std::uint32_t MATERIAL_SET = 1;
// set 0 of the scene: the block of the scene (gl::scene_ubs, dynamic offset in the ring of the frame)
constexpr std::uint32_t SCENE_UBO = 0;
// the blocks of the draws of the frame (gl::model_ubs each), the one of a draw picked by the push constant draw
constexpr std::uint32_t DRAW_SSBO = 1;
// the modelview matrices of the instances of the frame, element 0 the identity (draws which aren't instanced)
constexpr std::uint32_t INSTANCE_SSBO = 2;
// the light buffer of the pass (gl::light_data, dynamic offset)
constexpr std::uint32_t LIGHT_SSBO = 3;
// the light clusters: the grid of the pass (gl::cluster_ubs, dynamic offset), the lists of the lights of the clusters
constexpr std::uint32_t CLUSTER_UBO = 4;
constexpr std::uint32_t CLUSTER_RANGE_SSBO = 5;
constexpr std::uint32_t CLUSTER_INDEX_SSBO = 6;
// the cascades of the sun shadows (depth compared), the environment map, the beams of the headlights
constexpr std::uint32_t SHADOW_TEX = 7;
constexpr std::uint32_t ENV_TEX = 8;
constexpr std::uint32_t HEADLIGHT_TEX = 9;
constexpr std::uint32_t PASS_BINDING_COUNT = 10;
// the textures of a material take the bindings of their units (#pragma texture(name, unit, ...)), 0 .. MAX_TEXTURES - 1
constexpr std::uint32_t MATERIAL_TEXTURE_FIRST = 0;
// the compute passes of the light clusters, set 0 pushed
constexpr std::uint32_t CL_CLUSTER_UBO = 0;
constexpr std::uint32_t CL_AABB_SSBO = 1;
constexpr std::uint32_t CL_RANGE_SSBO = 2;
constexpr std::uint32_t CL_INDEX_SSBO = 3;
constexpr std::uint32_t CL_COUNTER_SSBO = 4;
constexpr std::uint32_t CL_LIGHT_SSBO = 5;
// the post-processing and the copies of whole images: set 0 pushed, the images it reads from binding 0 on
constexpr std::uint32_t POST_SET = 0;
constexpr std::uint32_t POST_TEXTURE_FIRST = 0;
// the push constants of the graphics pipelines of the scene and of the post-processing (vk14::draw_constants): the draw, and
// the parameters of the special shaders (the celestial bodies, the post-processing)
constexpr std::uint32_t PUSH_CONSTANTS_SIZE = 112;
// the specialization constants of the shaders
// 1: the colour written to the window is encoded by the shader (a window without an srgb format, gfx.shadergamma yes)
constexpr std::uint32_t SPEC_OUTPUT_GAMMA = 0;
// the settings of the game (gfx.* in eu07.ini) the shaders of the scene depend on, as in shaders/ogl46/settings.glsl
constexpr std::uint32_t SPEC_SHADOWMAP_ENABLED = 1;
constexpr std::uint32_t SPEC_ENVMAP_ENABLED = 2;
constexpr std::uint32_t SPEC_MOTIONBLUR_ENABLED = 3;
constexpr std::uint32_t SPEC_POSTFX_ENABLED = 4;
constexpr std::uint32_t SPEC_EXTRAEFFECTS_ENABLED = 5;
constexpr std::uint32_t SPEC_COUNT = 6;
} // namespace vk14::bindings
namespace vk14
{
// the bindings above as #define lines, with MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
inline std::string binding_preamble()
{
auto const define = [](char const *Name, std::uint32_t const Value) { return std::string("#define ") + Name + " " + std::to_string(Value) + "\n"; };
return define("MASZYNA_CONSTANTS", 1) + define("UI_SET", bindings::UI_SET) + define("UI_TEXTURE", bindings::UI_TEXTURE) + define("PASS_SET", bindings::PASS_SET) + define("MATERIAL_SET", bindings::MATERIAL_SET) +
define("MATERIAL_TEXTURE_FIRST", bindings::MATERIAL_TEXTURE_FIRST) + define("SPEC_OUTPUT_GAMMA", bindings::SPEC_OUTPUT_GAMMA);
}
// the bindings above as #define lines, with the constants of the game the shaders share with the opengl renderer (the sizes of
// the blocks, the cluster grid), and MASZYNA_CONSTANTS, which tells shaders/vk14/common.glsl not to write its own
std::string binding_preamble();
} // namespace vk14

View File

@@ -0,0 +1,322 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the objects of the vulkan api of the vulkan renderer (vulkan14_renderer::context), for the files of the renderer only:
// the core (vulkan14renderer.cpp) and the scene (vulkan14scene.cpp)
#pragma once
#include "rendering/vulkan14/vulkan14renderer.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14geometry.h"
#include "model/material.h"
#include "gl/ubo.h"
#include <array>
#include <memory>
#include <unordered_map>
#include <vector>
namespace vk14
{
// how the vertices of a program of the scene come
enum class vertex_kind : std::uint8_t
{
none, // made by the vertex shader (the celestial bodies, the full screen quads)
scene, // gfx::basic_vertex and the user data, the geometry banks
colour, // position and colour, the sky dome
particle, // position, colour and texture coordinates in one stream, the smoke
position_uv // position and texture coordinates in two streams, the precipitation
};
// the push constants of the pipelines of the scene and of the post-processing (shaders/vk14/common.glsl, draw_constants_block)
struct draw_constants
{
std::uint32_t draw{0};
std::uint32_t flags{0};
std::uint32_t pad0{0};
std::uint32_t pad1{0};
glm::vec4 data[6]{};
};
static_assert(sizeof(draw_constants) == bindings::PUSH_CONSTANTS_SIZE, "the push constants don't match the shaders");
// a program of the scene: the shaders and the pipelines made of them for the targets and the states they were drawn with
struct scene_program : public gfx::shader_program
{
std::shared_ptr<shader_code const> vertex;
std::shared_ptr<shader_code const> fragment;
// the fragment shader of the passes without the velocity target (NO_MOTION_OUTPUT), made when it's first needed
std::string fragment_name;
std::shared_ptr<shader_code const> fragment_single;
vertex_kind vertices{vertex_kind::scene};
std::unordered_map<std::uint64_t, VkPipeline> pipelines;
};
// what a pass of the scene draws to; the pipelines are made for these
struct target_formats
{
std::array<VkFormat, 2> colour{VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED};
std::uint32_t colours{0};
VkFormat depth{VK_FORMAT_UNDEFINED};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
// the number the pipelines of the targets are known by
std::uint32_t id{0};
};
// the images a viewport draws its scene to, made for its size and the settings
struct viewport_targets
{
VkExtent2D extent{0, 0};
VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT};
bool motion{false}; // the velocity is drawn, for the motion blur
bool aberration{false};
// with more than one sample: the images drawn to, resolved to the ones below
std::shared_ptr<image> msaa_colour;
std::shared_ptr<image> msaa_motion;
std::shared_ptr<image> msaa_depth;
// the colour of the scene (hdr), the velocity and the depth: the resolved ones, or the ones drawn to with one sample
std::shared_ptr<image> colour;
std::shared_ptr<image> motion_image;
std::shared_ptr<image> depth;
// the colour after the motion blur (hdr)
std::shared_ptr<image> blurred;
// the tonemapped image, before the chromatic aberration, and the one which goes to the window
std::shared_ptr<image> tonemapped;
std::shared_ptr<image> output;
// the depth under the cursor of the editor: one sample of the depth of the ground resolved to it, and the buffer it's read back with
std::shared_ptr<image> cursor_depth;
target_formats formats;
target_formats output_formats;
bool srgb{true}; // the output encodes the colour (gfx.shadergamma no)
};
// the ring of a frame some data of the draws goes through, the descriptor of which reads a window of it: the window starts again
// further on when it's full, and the pass set is bound again with the new offset
struct window_ring
{
frame_ring ring;
VkDeviceSize window{0}; // the size of the window, bytes
VkDeviceSize stride{0};
VkDeviceSize base{0}; // where the current window starts
bool open{false};
};
// the light clusters (gl::cluster_grid), of the colour pass of each viewport in turn
struct cluster_state
{
buffer aabbs;
buffer ranges;
buffer indices;
buffer counters;
gl::cluster_ubs params{};
bool stale{true}; // the boxes have to be made anew
VkDeviceSize params_offset{0}; // in the ring of the frame, for the pass under way
std::uint32_t lights_total{0};
};
// a picking or a reading of a value of the depth, waiting for the gpu to be done with the frame it was made in
struct readback
{
std::shared_ptr<buffer> data;
std::uint64_t value{0}; // of the timeline
bool pending{false};
glm::ivec2 position{0};
};
// what the scene is drawn with
struct scene_state
{
// the layouts: of the pass set (set 0), the pushed set of the materials (set 1), of the pipelines of the scene, the
// post-processing and the compute passes of the clusters
VkDescriptorSetLayout pass_layout{VK_NULL_HANDLE};
push_set material_set;
VkPipelineLayout scene_layout{VK_NULL_HANDLE};
push_set post_set;
VkPipelineLayout post_layout{VK_NULL_HANDLE};
push_set cluster_set;
VkPipelineLayout cluster_layout{VK_NULL_HANDLE};
// the pass sets: made with the pool when the images they read are made anew
VkDescriptorPool pass_pool{VK_NULL_HANDLE};
enum pass_set_kind
{
set_colour, // the shadow map and the environment map
set_reflections, // the shadow map and the empty environment map
set_plain, // the empty maps (shadows, picking)
set_count
};
std::array<VkDescriptorSet, set_count> pass_sets{};
bool pass_sets_stale{true};
// the rings of the draws: the blocks of the models (gl::model_ubs) and the modelview matrices of the instances
window_ring draws;
window_ring instances;
// the images every pass may read: the stand-ins, the shadow maps, the environment map
std::shared_ptr<image> white; // a texture of a material which isn't there
std::shared_ptr<image> shadow_empty;
std::shared_ptr<image> shadow_map;
std::vector<VkImageView> shadow_layers; // a view of each cascade
std::uint32_t shadow_size{0};
std::shared_ptr<image> env_empty;
std::shared_ptr<image> env_map;
std::shared_ptr<image> env_depth;
std::array<VkImageView, 6> env_faces{};
std::shared_ptr<image> pick_colour;
std::shared_ptr<image> pick_depth;
VkImageView headlights_view{VK_NULL_HANDLE}; // the one the pass sets were made with
cluster_state clusters;
// the targets of the viewports, [0] the main window
std::vector<viewport_targets> targets;
// the formats of the passes which don't depend on the viewport
target_formats shadow_formats;
target_formats reflection_formats;
target_formats pick_formats;
std::uint32_t formats_count{0};
// the programs: of the materials (by the name Fetch_Shader() is given), and the ones of the renderer
std::unordered_map<std::string, std::shared_ptr<scene_program>> materials;
std::shared_ptr<scene_program> shadow, alpha_shadow, pick, pick_surface, line, billboard, freespot, celestial, skydome, smoke, precipitation;
// the post-processing, by the fragment shader
std::unordered_map<std::string, std::shared_ptr<shader_code const>> post_fragments;
std::shared_ptr<shader_code const> quad;
VkPipeline cluster_build{VK_NULL_HANDLE};
VkPipeline cluster_cull{VK_NULL_HANDLE};
// the reading of the colour under the cursor of the picking passes and of the depth under the cursor of the editor
readback pick_controls;
readback pick_scenery;
readback cursor;
// the screenshot asked for, copied with the frame
std::shared_ptr<buffer> screenshot;
std::uint64_t screenshot_value{0};
VkExtent2D screenshot_extent{0, 0};
bool screenshot_pending{false};
bool screenshot_wanted{false};
// the occlusion queries of the points of light of the lamps, a pool for each frame in flight with the submodels they were made
// for; the glare of a lamp is drawn where its point was seen in the frame the gpu was done with last
std::vector<VkQueryPool> occlusion_pools;
std::vector<std::vector<TSubModel const *>> occlusion_submodels;
std::unordered_map<TSubModel const *, bool> glare_visible;
static constexpr std::uint32_t occlusion_queries{1024};
// the images of the scene were put in their layouts (the stand-ins cleared)
bool images_ready{false};
};
} // namespace vk14
// the objects of the vulkan api. destroyed in the reverse order: the swapchains and the frames, then the device, then the instance
struct vulkan14_renderer::context
{
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
std::uint64_t number{0};
};
vk14::instance instance;
vk14::device device;
vk14::allocator memory;
vk14::deletion_queue deletions;
vk14::uploader uploads;
vk14::image_table images;
vk14::samplers sampling;
vk14::frame_ring ring;
vk14::shader_library shaders;
vk14::pipeline_library pipelines;
vk14::pass_list passes;
vk14::pending_textures pending;
vk14::resources resources;
vk14::imgui_backend imgui;
vk14::scene_state scene;
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
// one semaphore counts the frames done by the gpu; the binary ones are only between the swapchains and the queue
VkSemaphore timeline{VK_NULL_HANDLE};
std::uint64_t timelinevalue{0};
// frames recorded since the start
std::uint64_t framecount{0};
// the windows with an image acquired in the frame being made
std::vector<vk14::swapchain *> acquired;
bool devicelost{false};
// the frame being recorded: its index among the frames in flight, its commands
std::uint32_t slot{0};
VkCommandBuffer commands{VK_NULL_HANDLE};
~context()
{
destroy();
}
bool create_frames(std::uint32_t const Count, std::string &Reason);
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason);
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t completed() const;
// waits until the gpu reached the value of the timeline; false: the device is lost
bool wait(std::uint64_t const Value) const;
void destroy();
};
// the state of the draws of the walk being recorded into a pass of the scene
struct vulkan14_renderer::draw_state
{
VkCommandBuffer commands{VK_NULL_HANDLE};
vk14::target_formats const *targets{nullptr};
VkExtent2D extent{0, 0};
vk14::scene_state::pass_set_kind set{vk14::scene_state::set_plain};
bool rendering{false}; // inside a rendering of a pass of the scene
bool shadows{false}; // a pass of the sun shadows: the back faces are drawn
// the offsets of the pass set in the rings of the frame
std::uint32_t scene_offset{0};
std::uint32_t light_offset{0};
std::uint32_t cluster_offset{0};
bool set_dirty{true};
// the program and the textures for the next draw (walk_material(), walk_program(), walk_texture())
vk14::scene_program *program{nullptr};
std::array<texture_handle, gl::MAX_TEXTURES> textures{};
bool textures_dirty{true};
// the block of the draw last pushed, for the chunks drawn together
std::uint32_t block{0};
// what is bound
VkPipeline pipeline{VK_NULL_HANDLE};
VkBuffer vertices{VK_NULL_HANDLE};
VkBuffer userdata{VK_NULL_HANDLE};
VkBuffer indices{VK_NULL_HANDLE};
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_MAX_ENUM};
VkCullModeFlags cull{VK_CULL_MODE_FLAG_BITS_MAX_ENUM};
int depthtest{-1};
int depthwrite{-1};
std::uint32_t pushed_draw{~0u};
// the state of the walk (setup_drawing() and the glare of the opengl renderer)
bool blending{false};
bool cullface{true};
bool glare{false};
bool depthmask_off{false}; // the editor overlay: no depth written whatever the blending
// the occlusion queries of the glare of the lamps in the frame: the submodels, by the index of their query
std::vector<TSubModel const *> queries;
std::uint32_t query{~0u}; // the query under way
// the draws made in the pass, for the debug panel
std::uint32_t draws{0};
};

View File

@@ -37,9 +37,10 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
}
if (Compare)
{
// reverse z: the fragment is lit where it's nearer the light than the map, its depth greater
// as GL_LEQUAL of the opengl renderer: 1 (in the shadow) where the reference isn't nearer the light than the map (reverse
// z: its depth isn't greater), the shaders take the result as the amount of the shadow
info.compareEnable = VK_TRUE;
info.compareOp = VK_COMPARE_OP_GREATER_OR_EQUAL;
info.compareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
}
if (!VK14_VERIFY(vkCreateSampler(handle, &info, nullptr, &Sampler)))
@@ -53,7 +54,7 @@ bool samplers::create(device const &Device, float Anisotropy, std::string &Reaso
};
return make(repeat_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_REPEAT, true, Anisotropy, false, "repeat aniso") && make(clamp_aniso, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, Anisotropy, false, "clamp aniso") &&
make(clamp_linear, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, true, 1.f, false, "clamp linear") && make(clamp_nearest, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, false, "clamp nearest") &&
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, false, 1.f, true, "shadow");
make(shadow, VK_FILTER_LINEAR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, false, 1.f, true, "shadow");
}
void samplers::destroy(VkDevice Device)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,280 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14geometry.h"
#include "rendering/vulkan14/vulkan14texture.h"
namespace vk14
{
namespace
{
// what opengl reads for the user data of a vertex without any (the attribute isn't given)
gfx::vertex_userdata const no_userdata{glm::vec4{0.f, 0.f, 0.f, 1.f}};
} // namespace
VkPrimitiveTopology topology_of(unsigned int const Type)
{
switch (Type)
{
case GL_POINTS:
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case GL_LINES:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case GL_LINE_STRIP:
case GL_LINE_LOOP:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case GL_TRIANGLE_STRIP:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case GL_TRIANGLE_FAN:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
case GL_TRIANGLES:
default:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
}
}
vulkan14_geometrybank::~vulkan14_geometrybank()
{
release_();
}
void vulkan14_geometrybank::create_(gfx::geometry_handle const &Geometry)
{
// a new chunk: the buffers are made anew with it
m_layoutdirty = true;
}
void vulkan14_geometrybank::replace_(gfx::geometry_handle const &Geometry)
{
if (m_layoutdirty)
{
return;
}
auto const index{Geometry.chunk - 1};
if (index >= m_places.size())
{
m_layoutdirty = true;
return;
}
auto const &chunk{gfx::geometry_bank::chunk(Geometry)};
auto &place{m_places[index]};
auto const loop{chunk.type == GL_LINE_LOOP};
auto const vertexcount{static_cast<std::uint32_t>(chunk.vertices.size()) + ((loop && chunk.indices.empty() && (false == chunk.vertices.empty())) ? 1u : 0u)};
auto const indexcount{static_cast<std::uint32_t>(chunk.indices.size()) + ((loop && (false == chunk.indices.empty())) ? 1u : 0u)};
if ((vertexcount != place.vertexcount) || (indexcount != place.indexcount))
{
// it doesn't fit in its place any more
m_layoutdirty = true;
return;
}
place.dirty = true;
m_chunksdirty = true;
}
void vulkan14_geometrybank::release_()
{
auto *deletions{m_resources.deletions};
if ((deletions != nullptr) && (m_resources.uploads != nullptr) && (m_vertices || m_userdata || m_indices))
{
deletions->push(m_resources.uploads->current_value(), [vertices = std::move(m_vertices), userdata = std::move(m_userdata), indices = std::move(m_indices)]() {});
}
m_vertices.reset();
m_userdata.reset();
m_indices.reset();
m_places.clear();
m_bytes = 0;
m_layoutdirty = true;
m_chunksdirty = false;
}
void vulkan14_geometrybank::gpu_vertices(geometry_chunk const &Chunk, std::vector<gfx::basic_vertex> &Vertices, std::vector<gfx::vertex_userdata> &Userdata) const
{
Vertices.insert(Vertices.end(), Chunk.vertices.begin(), Chunk.vertices.end());
if (Chunk.userdata.size() >= Chunk.vertices.size())
{
Userdata.insert(Userdata.end(), Chunk.userdata.begin(), Chunk.userdata.begin() + Chunk.vertices.size());
}
else
{
Userdata.insert(Userdata.end(), Chunk.userdata.begin(), Chunk.userdata.end());
Userdata.resize(Vertices.size(), no_userdata);
}
if ((Chunk.type == GL_LINE_LOOP) && Chunk.indices.empty() && (false == Chunk.vertices.empty()))
{
// back to the first vertex
Vertices.push_back(Chunk.vertices.front());
Userdata.push_back(Chunk.userdata.empty() ? no_userdata : Chunk.userdata.front());
}
}
bool vulkan14_geometrybank::build()
{
release_();
std::vector<gfx::basic_vertex> vertices;
std::vector<gfx::vertex_userdata> userdata;
std::vector<gfx::basic_index> indices;
m_places.resize(m_chunks.size());
for (std::size_t i = 0; i < m_chunks.size(); ++i)
{
auto const &chunk{m_chunks[i]};
auto &place{m_places[i]};
place.vertexfirst = static_cast<std::uint32_t>(vertices.size());
place.indexfirst = static_cast<std::uint32_t>(indices.size());
gpu_vertices(chunk, vertices, userdata);
place.vertexcount = static_cast<std::uint32_t>(vertices.size()) - place.vertexfirst;
indices.insert(indices.end(), chunk.indices.begin(), chunk.indices.end());
if ((chunk.type == GL_LINE_LOOP) && (false == chunk.indices.empty()))
{
indices.push_back(chunk.indices.front());
}
place.indexcount = static_cast<std::uint32_t>(indices.size()) - place.indexfirst;
place.dirty = false;
}
m_layoutdirty = false;
m_chunksdirty = false;
if (vertices.empty())
{
return false;
}
auto &memory{*m_resources.memory};
auto &uploads{*m_resources.uploads};
auto const make = [&](std::shared_ptr<buffer> &Buffer, void const *Data, VkDeviceSize const Size, VkBufferUsageFlags const Usage, char const *Name) {
Buffer = std::make_shared<buffer>();
if ((false == Buffer->create(memory, Size, Usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT, memory_kind::device, Name)) || (false == uploads.upload_buffer(Buffer, Data, Size)))
{
Buffer.reset();
return false;
}
m_bytes += Size;
return true;
};
auto ok{make(m_vertices, vertices.data(), vertices.size() * sizeof(gfx::basic_vertex), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "geometry vertices") &&
make(m_userdata, userdata.data(), userdata.size() * sizeof(gfx::vertex_userdata), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, "geometry user data")};
if (ok && (false == indices.empty()))
{
ok = make(m_indices, indices.data(), indices.size() * sizeof(gfx::basic_index), VK_BUFFER_USAGE_INDEX_BUFFER_BIT, "geometry indices");
}
if (false == ok)
{
log_error("no memory for the geometry of a bank of " + std::to_string(vertices.size()) + " vertices");
release_();
// tried again when it's drawn the next time
m_layoutdirty = true;
return false;
}
return true;
}
void vulkan14_geometrybank::update()
{
m_chunksdirty = false;
if ((false == bool(m_vertices)) || (false == bool(m_userdata)))
{
return;
}
std::vector<gfx::basic_vertex> vertices;
std::vector<gfx::vertex_userdata> userdata;
std::vector<gfx::basic_index> indices;
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> vertexparts, userdataparts, indexparts;
for (std::size_t i = 0; i < m_places.size(); ++i)
{
auto &place{m_places[i]};
if (false == place.dirty)
{
continue;
}
place.dirty = false;
auto const &chunk{m_chunks[i]};
gpu_vertices(chunk, vertices, userdata);
vertexparts.emplace_back(place.vertexfirst * sizeof(gfx::basic_vertex), place.vertexcount * sizeof(gfx::basic_vertex));
userdataparts.emplace_back(place.vertexfirst * sizeof(gfx::vertex_userdata), place.vertexcount * sizeof(gfx::vertex_userdata));
if ((place.indexcount > 0) && m_indices)
{
indices.insert(indices.end(), chunk.indices.begin(), chunk.indices.end());
if (chunk.type == GL_LINE_LOOP)
{
indices.push_back(chunk.indices.front());
}
indexparts.emplace_back(place.indexfirst * sizeof(gfx::basic_index), place.indexcount * sizeof(gfx::basic_index));
}
}
auto &uploads{*m_resources.uploads};
if (false == vertices.empty())
{
uploads.upload_buffer(m_vertices, vertices.data(), vertices.size() * sizeof(gfx::basic_vertex), vertexparts);
uploads.upload_buffer(m_userdata, userdata.data(), userdata.size() * sizeof(gfx::vertex_userdata), userdataparts);
}
if (false == indices.empty())
{
uploads.upload_buffer(m_indices, indices.data(), indices.size() * sizeof(gfx::basic_index), indexparts);
}
}
bool vulkan14_geometrybank::source(gfx::geometry_handle const &Geometry, chunk_source &Source)
{
if ((Geometry.chunk == 0) || (Geometry.chunk > m_chunks.size()))
{
return false;
}
if (m_layoutdirty)
{
build();
}
else if (m_chunksdirty)
{
update();
}
auto const index{Geometry.chunk - 1};
if ((false == bool(m_vertices)) || (index >= m_places.size()))
{
return false;
}
auto const &place{m_places[index]};
Source.vertices = m_vertices->handle();
Source.userdata = m_userdata->handle();
Source.topology = topology_of(m_chunks[index].type);
if (place.indexcount > 0)
{
Source.indices = m_indices->handle();
Source.count = place.indexcount;
Source.first = place.indexfirst;
Source.basevertex = static_cast<std::int32_t>(place.vertexfirst);
}
else
{
Source.indices = VK_NULL_HANDLE;
Source.count = place.vertexcount;
Source.first = place.vertexfirst;
Source.basevertex = 0;
}
return Source.count > 0;
}
bool vulkan14_geometrybank::indirect_source_(gfx::geometry_handle const &Geometry, gfx::indirect_source &Source)
{
m_last = chunk_source{};
if (false == source(Geometry, m_last))
{
return false;
}
Source.count = m_last.count;
Source.first = m_last.first;
Source.basevertex = m_last.basevertex;
Source.indexed = (m_last.indices != VK_NULL_HANDLE);
Source.type = m_chunks[Geometry.chunk - 1].type;
return true;
}
} // namespace vk14

View File

@@ -0,0 +1,99 @@
/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the geometry banks of the vulkan renderer (stage D1 of the plan): the chunks of a bank in three device buffers of the bank
// (vertices, their user data, 32 bit indices), made the first time the bank is drawn after its chunks changed. a chunk replaced
// with the same size is copied in place, otherwise the buffers are made anew and the old ones go once the gpu is done with them.
// the data stays in memory as well (gfx::geometry_bank), the simulation reads it back. the primitives are the values of opengl
// the program gives them with; a line loop is drawn as a strip which goes back to its first vertex
#pragma once
#include "rendering/geometrybank.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include <memory>
namespace vk14
{
struct resources;
// where the data of a chunk is in the buffers of its bank
struct chunk_source
{
VkBuffer vertices{VK_NULL_HANDLE};
VkBuffer userdata{VK_NULL_HANDLE};
VkBuffer indices{VK_NULL_HANDLE}; // null for a chunk without indices
std::uint32_t count{0}; // vertices, or indices
std::uint32_t first{0}; // first index, or vertex
std::int32_t basevertex{0};
VkPrimitiveTopology topology{VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST};
};
// the vulkan primitive of a primitive of opengl (GL_TRIANGLES ...)
VkPrimitiveTopology topology_of(unsigned int Type);
class vulkan14_geometrybank : public gfx::geometry_bank
{
public:
explicit vulkan14_geometrybank(resources const &Resources) : m_resources(Resources) {}
~vulkan14_geometrybank() override;
// the chunk with its data on the gpu: uploaded now when it changed since the last time. false: nothing to draw.
// gfx::geometrybank_manager::indirect_source() calls it too (which marks the bank as used), and keeps its answer in last()
bool source(gfx::geometry_handle const &Geometry, chunk_source &Source);
chunk_source const &last() const
{
return m_last;
}
// the bytes on the gpu, for the debug panel
VkDeviceSize gpu_bytes() const
{
return m_bytes;
}
private:
struct chunk_place
{
std::uint32_t vertexfirst{0};
std::uint32_t vertexcount{0}; // on the gpu: one more than the chunk for a line loop
std::uint32_t indexfirst{0};
std::uint32_t indexcount{0};
bool dirty{true};
};
void create_(gfx::geometry_handle const &Geometry) override;
void replace_(gfx::geometry_handle const &Geometry) override;
std::size_t draw_(gfx::geometry_handle const &Geometry, gfx::stream_units const &Units, unsigned int const Streams) override
{
return 0;
}
bool indirect_source_(gfx::geometry_handle const &Geometry, gfx::indirect_source &Source) override;
void release_() override;
// the buffers made anew for all the chunks
bool build();
// the chunks changed in place copied to the buffers
void update();
// the vertices of a chunk as they go to the gpu
void gpu_vertices(geometry_chunk const &Chunk, std::vector<gfx::basic_vertex> &Vertices, std::vector<gfx::vertex_userdata> &Userdata) const;
resources const &m_resources;
std::shared_ptr<buffer> m_vertices;
std::shared_ptr<buffer> m_userdata;
std::shared_ptr<buffer> m_indices;
std::vector<chunk_place> m_places;
bool m_layoutdirty{true}; // the buffers have to be made anew
bool m_chunksdirty{false}; // some chunks have to be copied
VkDeviceSize m_bytes{0};
chunk_source m_last;
};
} // namespace vk14

View File

@@ -177,6 +177,14 @@ void buffer::flush(VkDeviceSize Offset, VkDeviceSize Size) const
}
}
void buffer::invalidate(VkDeviceSize Offset, VkDeviceSize Size) const
{
if ((m_allocation != VK_NULL_HANDLE) && (Size > 0))
{
vmaInvalidateAllocation(m_allocator, m_allocation, Offset, Size);
}
}
// image
image::~image()
@@ -334,12 +342,12 @@ void deletion_queue::flush()
// frame ring
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name)
bool frame_ring::create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name, VkDeviceSize Tail)
{
m_sizeperframe = SizePerFrame;
m_base = 0;
m_used = 0;
return m_buffer.create(Allocator, SizePerFrame * Frames, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
return m_buffer.create(Allocator, SizePerFrame * Frames + Tail, Usage, memory_kind::upload, Name) && (m_buffer.mapped() != nullptr);
}
void frame_ring::destroy()

View File

@@ -91,6 +91,8 @@ class buffer
}
// makes the writes of the cpu seen by the gpu where the memory isn't coherent
void flush(VkDeviceSize Offset, VkDeviceSize Size) const;
// makes the writes of the gpu seen by the cpu where the memory isn't coherent (readback memory may be cached)
void invalidate(VkDeviceSize Offset, VkDeviceSize Size) const;
explicit operator bool() const
{
return m_buffer != VK_NULL_HANDLE;
@@ -214,7 +216,8 @@ struct ring_slice
class frame_ring
{
public:
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name);
// Tail: bytes past the part of the last frame, so a descriptor of a fixed range can be given an offset anywhere in a part
bool create(allocator const &Allocator, VkDeviceSize SizePerFrame, std::uint32_t Frames, VkBufferUsageFlags Usage, std::string const &Name, VkDeviceSize Tail = 0);
void destroy();
// the part of the frame Frame is used from its start again
void begin_frame(std::uint32_t Frame);
@@ -226,10 +229,20 @@ class frame_ring
{
return m_used;
}
// the offset in the buffer the next allocation aligned to Alignment would get
VkDeviceSize next_offset(VkDeviceSize Alignment) const
{
auto const alignment{std::max<VkDeviceSize>(Alignment, 4)};
return m_base + (m_used + alignment - 1) / alignment * alignment;
}
VkDeviceSize capacity() const
{
return m_sizeperframe;
}
VkBuffer handle() const
{
return m_buffer.handle();
}
private:
buffer m_buffer;

View File

@@ -8,18 +8,7 @@ http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14renderer.h"
#include "rendering/vulkan14/vulkan14common.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14memory.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14descriptors.h"
#include "rendering/vulkan14/vulkan14passes.h"
#include "rendering/vulkan14/vulkan14texture.h"
#include "rendering/vulkan14/vulkan14imgui.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "rendering/vulkan14/vulkan14context.h"
#include "rendering/nullrenderer.h"
#include "application/application.h"
#include "simulation/simulation.h"
@@ -37,8 +26,11 @@ namespace
// background of the opengl renderer (srgb). before that the windows are black, as with the opengl renderer
glm::vec3 const clear_colour{51.f / 255.f, 102.f / 255.f, 85.f / 255.f};
// the ring each frame in flight streams its data through (the user interface for now)
VkDeviceSize const ring_size{8 * 1024 * 1024};
// the ring each frame in flight streams its data through: the user interface, the blocks of the scene and of the light clusters,
// the light buffers of the passes, the vertices of the particles and the sky; the descriptors read the light buffers with the range
// of the whole gl::light_data, which the tail past the last frame leaves room for
VkDeviceSize const ring_size{16 * 1024 * 1024};
VkDeviceSize const ring_tail{(sizeof(gl::light_data) + 65535) / 65536 * 65536};
float srgb_to_linear(float const Value)
{
@@ -47,192 +39,157 @@ float srgb_to_linear(float const Value)
} // namespace
// the objects of the vulkan api. destroyed in the reverse order: the swapchains and the frames, then the device, then the instance
struct vulkan14_renderer::context
bool vulkan14_renderer::context::create_frames(std::uint32_t const Count, std::string &Reason)
{
struct frame
{
VkCommandPool pool{VK_NULL_HANDLE};
// the uploads of the frame go to the queue first, then the commands of the frame
VkCommandBuffer uploads{VK_NULL_HANDLE};
VkCommandBuffer commands{VK_NULL_HANDLE};
// the value of the timeline semaphore the gpu reaches when it's done with the frame; 0: never submitted
std::uint64_t done{0};
// the number of the frame, for the report of a lost device
std::uint64_t number{0};
};
auto const handle{device.handle()};
vk14::instance instance;
vk14::device device;
vk14::allocator memory;
vk14::deletion_queue deletions;
vk14::uploader uploads;
vk14::image_table images;
vk14::samplers sampling;
vk14::frame_ring ring;
vk14::shader_library shaders;
vk14::pipeline_library pipelines;
vk14::pass_list passes;
vk14::pending_textures pending;
vk14::resources resources;
vk14::imgui_backend imgui;
// the textures of the files; made after the objects above and gone before them
std::unique_ptr<texture_bank> textures;
// [0]: the main window, then the windows of the extra viewports
std::vector<std::unique_ptr<vk14::swapchain>> windows;
std::vector<frame> frames;
// one semaphore counts the frames done by the gpu; the binary ones are only between the swapchains and the queue
VkSemaphore timeline{VK_NULL_HANDLE};
std::uint64_t timelinevalue{0};
// frames recorded since the start
std::uint64_t framecount{0};
// the windows with an image acquired in the frame being made
std::vector<vk14::swapchain *> acquired;
bool devicelost{false};
~context()
VkSemaphoreTypeCreateInfo timelineinfo{};
timelineinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
timelineinfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
timelineinfo.initialValue = 0;
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreinfo.pNext = &timelineinfo;
if (!VK14_VERIFY(vkCreateSemaphore(handle, &semaphoreinfo, nullptr, &timeline)))
{
destroy();
Reason = "no timeline semaphore";
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(timeline), "frame timeline");
bool create_frames(std::uint32_t const Count, std::string &Reason)
frames.resize(Count);
for (std::uint32_t i = 0; i < Count; ++i)
{
auto const handle{device.handle()};
VkSemaphoreTypeCreateInfo timelineinfo{};
timelineinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
timelineinfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
timelineinfo.initialValue = 0;
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
semaphoreinfo.pNext = &timelineinfo;
if (!VK14_VERIFY(vkCreateSemaphore(handle, &semaphoreinfo, nullptr, &timeline)))
VkCommandPoolCreateInfo poolinfo{};
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolinfo.queueFamilyIndex = device.families().graphics;
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &frames[i].pool)))
{
Reason = "no timeline semaphore";
Reason = "no command pool";
return false;
}
instance.set_object_name(handle, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(timeline), "frame timeline");
frames.resize(Count);
for (std::uint32_t i = 0; i < Count; ++i)
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
{
VkCommandPoolCreateInfo poolinfo{};
poolinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolinfo.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT;
poolinfo.queueFamilyIndex = device.families().graphics;
if (!VK14_VERIFY(vkCreateCommandPool(handle, &poolinfo, nullptr, &frames[i].pool)))
{
Reason = "no command pool";
return false;
}
VkCommandBuffer buffers[2] = {VK_NULL_HANDLE, VK_NULL_HANDLE};
VkCommandBufferAllocateInfo bufferinfo{};
bufferinfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
bufferinfo.commandPool = frames[i].pool;
bufferinfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
bufferinfo.commandBufferCount = 2;
if (!VK14_VERIFY(vkAllocateCommandBuffers(handle, &bufferinfo, buffers)))
{
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
Reason = "no command buffer";
return false;
}
frames[i].uploads = buffers[0];
frames[i].commands = buffers[1];
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].uploads), "uploads " + std::to_string(i));
instance.set_object_name(handle, VK_OBJECT_TYPE_COMMAND_BUFFER, reinterpret_cast<std::uint64_t>(frames[i].commands), "frame " + std::to_string(i));
}
return true;
}
// the parts of the renderer made once the device is there
bool vulkan14_renderer::context::create_core(std::uint32_t const Frames, std::string &Reason)
{
if (false == memory.create(instance, device, Reason))
{
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
{
return false;
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring", ring_tail))
{
Reason = "no ring for the data of the frames";
return false;
}
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
{
return false;
}
resources.gpu = &device;
resources.memory = &memory;
resources.uploads = &uploads;
resources.deletions = &deletions;
resources.sampling = &sampling;
resources.images = &images;
resources.thread = std::this_thread::get_id();
resources.pending = &pending;
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
return imgui.create(resources, shaders, pipelines, ring, Reason);
}
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t vulkan14_renderer::context::completed() const
{
std::uint64_t value{0};
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
{
return 0;
}
return value;
}
bool vulkan14_renderer::context::wait(std::uint64_t const Value) const
{
if ((Value == 0) || (completed() >= Value))
{
return true;
}
VkSemaphoreWaitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
wait.semaphoreCount = 1;
wait.pSemaphores = &timeline;
wait.pValues = &Value;
return VK14_VERIFY(vkWaitSemaphores(device.handle(), &wait, UINT64_MAX));
}
// the parts of the renderer made once the device is there
bool create_core(std::uint32_t const Frames, std::string &Reason)
void vulkan14_renderer::context::destroy()
{
auto const handle{device.handle()};
if (handle != VK_NULL_HANDLE)
{
if (false == memory.create(instance, device, Reason))
{
return false;
}
uploads.init(memory, deletions, device.physical());
if (false == sampling.create(device, static_cast<float>(Global.AnisotropicFiltering), Reason))
{
return false;
}
auto const &limits{device.caps().limits};
(void)limits;
if (false == ring.create(memory, ring_size, Frames, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, "frame ring"))
{
Reason = "no ring for the data of the frames";
return false;
}
if ((false == shaders.init(vk14::binding_preamble(), Reason)) || (false == pipelines.init(device, Reason)) || (false == passes.create(device, Frames, Reason)))
{
return false;
}
resources.gpu = &device;
resources.memory = &memory;
resources.uploads = &uploads;
resources.deletions = &deletions;
resources.sampling = &sampling;
resources.images = &images;
resources.thread = std::this_thread::get_id();
resources.pending = &pending;
textures = std::make_unique<texture_bank>([this]() -> texture_source * { return new vk14::vulkan14_texture(resources); });
return imgui.create(resources, shaders, pipelines, ring, Reason);
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(handle);
}
// the value of the timeline the gpu reached, 0 when it can't be read
std::uint64_t completed() const
acquired.clear();
windows.clear();
imgui.destroy();
// the textures give their images to the deletion queue, which goes now that the gpu is idle
textures.reset();
uploads.end_frame();
uploads = vk14::uploader{};
images.clear();
deletions.flush();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
ring.destroy();
if (handle != VK_NULL_HANDLE)
{
std::uint64_t value{0};
if ((timeline == VK_NULL_HANDLE) || (vkGetSemaphoreCounterValue(device.handle(), timeline, &value) != VK_SUCCESS))
sampling.destroy(handle);
for (auto &frame : frames)
{
return 0;
}
return value;
}
void destroy()
{
auto const handle{device.handle()};
if (handle != VK_NULL_HANDLE)
{
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(handle);
}
acquired.clear();
windows.clear();
imgui.destroy();
// the textures give their images to the deletion queue, which goes now that the gpu is idle
textures.reset();
uploads.end_frame();
uploads = vk14::uploader{};
images.clear();
deletions.flush();
pipelines.shutdown();
shaders.shutdown();
passes.destroy();
ring.destroy();
if (handle != VK_NULL_HANDLE)
{
sampling.destroy(handle);
for (auto &frame : frames)
if (frame.pool != VK_NULL_HANDLE)
{
if (frame.pool != VK_NULL_HANDLE)
{
vkDestroyCommandPool(handle, frame.pool, nullptr);
}
}
if (timeline != VK_NULL_HANDLE)
{
vkDestroySemaphore(handle, timeline, nullptr);
vkDestroyCommandPool(handle, frame.pool, nullptr);
}
}
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
instance.destroy();
if (timeline != VK_NULL_HANDLE)
{
vkDestroySemaphore(handle, timeline, nullptr);
}
}
};
memory.destroy();
frames.clear();
timeline = VK_NULL_HANDLE;
device.destroy();
instance.destroy();
}
namespace
{
@@ -320,10 +277,20 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
return false;
}
if (false == init_scene(reason))
{
vk14::log_error("the renderer can't start: " + reason);
destroy_scene();
m_context.reset();
return false;
}
init_frame_data();
vk14::log_info(std::to_string(framesinflight) + " frames in flight");
// until the stages of the plan bring them, these parts of the simulator aren't there with vulkan
vk14::log_info("not available with vulkan yet: the scenery and the vehicles (D1-D7), picking, mouse position in the scene and screenshots (D6), the map panel, "
"python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
vk14::log_info("not available with vulkan yet: the atmosphere, the sky ambient light, automatic exposure and bloom, stars (P1), the weather fog and clouds (P2), "
"ambient occlusion and contact shadows (P3), shadows of lamps and headlights (P4), the culling on the gpu (P5), the heightmap terrain, grass and the particles "
"of the gpu (P6), the map panel, python screens in their own windows (N5), panels in their own windows (R5b), VR (X4)");
WriteLog("Gfx Renderer: setup complete");
update_info();
return true;
@@ -331,38 +298,64 @@ bool vulkan14_renderer::Init(GLFWwindow *Window)
bool vulkan14_renderer::AddViewport(global_settings::extraviewport_config const &conf)
{
if (!m_context)
if (!m_context || m_viewports.empty())
{
return false;
}
viewport_config *viewport{nullptr};
if (conf.monitor == "MAIN")
{
// the projection of the main window is changed only, which the drawing of the scenery takes care of (D6)
vk14::log_info("extra viewport on the main window: drawn from the stage D6 of the plan");
return true;
// the main window drawn with the projection of the viewport
viewport = &m_viewports.front();
}
// a window of its own, without an opengl context
auto *window{Application.window(-1, true, conf.width, conf.height, Application.find_monitor(conf.monitor), true, false)};
if (window == nullptr)
else
{
vk14::log_error("the window of the extra viewport on " + conf.monitor + " couldn't be made");
return false;
// a window of its own, without an opengl context, with a swapchain of its own
auto *window{Application.window(-1, true, conf.width, conf.height, Application.find_monitor(conf.monitor), true, false)};
if (window == nullptr)
{
vk14::log_error("the window of the extra viewport on " + conf.monitor + " couldn't be made");
return false;
}
auto &ctx{*m_context};
auto swapchain{std::make_unique<vk14::swapchain>()};
std::string reason;
if (false == swapchain->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
{
vk14::log_error(reason);
return false;
}
ctx.windows.emplace_back(std::move(swapchain));
m_viewports.emplace_back();
viewport = &m_viewports.back();
viewport->window = ctx.windows.size() - 1;
}
auto &ctx{*m_context};
auto viewport{std::make_unique<vk14::swapchain>()};
std::string reason;
if (false == viewport->create(ctx.instance, ctx.device, window, "viewport " + conf.monitor, static_cast<std::uint32_t>(ctx.frames.size()), reason))
{
vk14::log_error(reason);
return false;
}
ctx.windows.emplace_back(std::move(viewport));
viewport->width = conf.width;
viewport->height = conf.height;
viewport->projection = conf.projection;
viewport->proj_type = viewport_config::custom;
viewport->draw_range = conf.draw_range;
vk14::log_info("viewport " + conf.monitor + ": " + std::to_string(conf.width) + "x" + std::to_string(conf.height));
return true;
}
void vulkan14_renderer::Shutdown()
{
if (m_context && (m_context->device.handle() != VK_NULL_HANDLE))
{
// the end of the renderer, not a frame: everything on the gpu has to be done
vkDeviceWaitIdle(m_context->device.handle());
}
// the geometry on the gpu goes with the context; the data of the banks stays for the simulation
for (auto *bank : m_banks)
{
if (bank != nullptr)
{
bank->release();
}
}
destroy_scene();
m_draw.reset();
m_context.reset();
}
@@ -387,184 +380,6 @@ void vulkan14_renderer::report_device_lost(char const *Where)
crashreport_add_info("vulkan_device_lost", report);
}
bool vulkan14_renderer::Render()
{
if (!m_context || m_context->devicelost)
{
return false;
}
auto &ctx{*m_context};
auto const device{ctx.device.handle()};
auto const slot{static_cast<std::uint32_t>(ctx.framecount % ctx.frames.size())};
auto &frame{ctx.frames[slot]};
Timer::subsystem.gfx_total.start();
// the gpu has to be done with the frame which used the slot before
if (frame.done > 0)
{
VkSemaphoreWaitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO;
wait.semaphoreCount = 1;
wait.pSemaphores = &ctx.timeline;
wait.pValues = &frame.done;
if (!VK14_VERIFY(vkWaitSemaphores(device, &wait, UINT64_MAX)))
{
report_device_lost("waiting for a frame");
return false;
}
ctx.passes.collect(slot);
}
// what the frames done used last goes
ctx.deletions.collect(std::max(ctx.completed(), frame.done));
VK14_VERIFY(vkResetCommandPool(device, frame.pool, 0));
ctx.ring.begin_frame(slot);
VkCommandBufferBeginInfo begin{};
begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK14_VERIFY(vkBeginCommandBuffer(frame.uploads, &begin));
VK14_VERIFY(vkBeginCommandBuffer(frame.commands, &begin));
auto const value{ctx.timelinevalue + 1};
ctx.uploads.begin_frame(frame.uploads, value);
// the content other threads gave the textures (python)
{
std::vector<vk14::vulkan14_texture *> pending;
{
std::lock_guard<std::mutex> lock(ctx.pending.mutex);
pending.swap(ctx.pending.textures);
}
for (auto *texture : pending)
{
texture->apply_pending();
}
}
ctx.acquired.clear();
for (auto &window : ctx.windows)
{
switch (window->acquire(slot))
{
case vk14::swapchain::acquire_result::ready:
ctx.acquired.push_back(window.get());
break;
case vk14::swapchain::acquire_result::skipped:
break;
default:
report_device_lost("acquiring an image");
return false;
}
}
// the user interface: imgui makes its frame, the images it asks for are uploaded in this frame
Timer::subsystem.gfx_gui.start();
Application.render_ui();
Timer::subsystem.gfx_gui.stop();
frame.number = ctx.framecount;
ctx.passes.begin_frame(frame.commands, slot, ctx.framecount);
auto const background{simulation::is_ready ? clear_colour : glm::vec3{0.f}};
bool uidrawn{false};
for (auto *window : ctx.acquired)
{
auto const colour{window->srgb() ? glm::vec3{srgb_to_linear(background.r), srgb_to_linear(background.g), srgb_to_linear(background.b)} : background};
if (window != ctx.windows.front().get())
{
// the extra viewports: their colour only, until the scenery is drawn into them (D6)
window->record_clear(frame.commands, glm::vec4{colour, 1.f});
continue;
}
// the main window: cleared, the user interface over it, shown
ctx.passes.begin_pass("ui", {window->use(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT)});
VkRenderingAttachmentInfo attachment{};
attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
attachment.imageView = window->view();
attachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.clearValue.color = {{colour.r, colour.g, colour.b, 1.f}};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, window->extent()};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &attachment;
vkCmdBeginRendering(frame.commands, &rendering);
ctx.imgui.record(frame.commands, window->extent(), window->format(), window->srgb());
vkCmdEndRendering(frame.commands);
ctx.passes.end_pass();
vk14::barriers(frame.commands, {window->use(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_2_NONE, VK_ACCESS_2_NONE)});
uidrawn = true;
}
if (false == uidrawn)
{
// the main window is minimized: the frame of imgui has nowhere to go
ctx.imgui.skip();
}
ctx.passes.end_frame();
auto const uploaded{ctx.uploads.end_frame()};
VK14_VERIFY(vkEndCommandBuffer(frame.uploads));
VK14_VERIFY(vkEndCommandBuffer(frame.commands));
ctx.ring.flush();
std::vector<VkSemaphoreSubmitInfo> waits;
std::vector<VkSemaphoreSubmitInfo> signals;
for (auto *window : ctx.acquired)
{
VkSemaphoreSubmitInfo wait{};
wait.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
wait.semaphore = window->acquire_semaphore(slot);
wait.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
waits.push_back(wait);
VkSemaphoreSubmitInfo signal{};
signal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
signal.semaphore = window->present_semaphore();
signal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(signal);
}
VkSemaphoreSubmitInfo timelinesignal{};
timelinesignal.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO;
timelinesignal.semaphore = ctx.timeline;
timelinesignal.value = value;
timelinesignal.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
signals.push_back(timelinesignal);
// the uploads first, in the same submit: their barriers make them done before the frame samples the images
std::vector<VkCommandBufferSubmitInfo> commands;
if (uploaded)
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.uploads;
commands.push_back(info);
}
{
VkCommandBufferSubmitInfo info{};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO;
info.commandBuffer = frame.commands;
commands.push_back(info);
}
VkSubmitInfo2 submit{};
submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2;
submit.waitSemaphoreInfoCount = static_cast<std::uint32_t>(waits.size());
submit.pWaitSemaphoreInfos = waits.data();
submit.commandBufferInfoCount = static_cast<std::uint32_t>(commands.size());
submit.pCommandBufferInfos = commands.data();
submit.signalSemaphoreInfoCount = static_cast<std::uint32_t>(signals.size());
submit.pSignalSemaphoreInfos = signals.data();
if (!VK14_VERIFY(vkQueueSubmit2(ctx.device.graphics_queue(), 1, &submit, VK_NULL_HANDLE)))
{
report_device_lost("submitting a frame");
return false;
}
frame.done = ctx.timelinevalue = value;
Timer::subsystem.gfx_total.stop();
return true;
}
void vulkan14_renderer::SwapBuffers()
{
if (!m_context)
@@ -616,143 +431,71 @@ float vulkan14_renderer::Framerate()
return m_framerate;
}
// geometry: kept in memory for the simulation, nothing on the gpu until D1
gfx::geometrybank_handle vulkan14_renderer::Create_Bank()
{
return m_geometry.register_bank(std::make_unique<null_geometrybank>());
}
gfx::geometry_handle vulkan14_renderer::Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
// NOTE: we expect indexed geometry to come with calculated tangents
return m_geometry.create_chunk(Indices, Vertices, Userdata, Geometry, Type);
}
gfx::geometry_handle vulkan14_renderer::Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.create_chunk(Vertices, Userdata, Geometry, Type);
}
bool vulkan14_renderer::Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.replace(Vertices, Userdata, Geometry, Offset);
}
bool vulkan14_renderer::Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type)
{
gfx::calculate_tangents(Vertices, gfx::index_array(), Type);
return m_geometry.append(Vertices, Userdata, Geometry);
}
gfx::index_array const &vulkan14_renderer::Indices(gfx::geometry_handle const &Geometry) const
{
return m_geometry.indices(Geometry);
}
gfx::vertex_array const &vulkan14_renderer::Vertices(gfx::geometry_handle const &Geometry) const
{
return m_geometry.vertices(Geometry);
}
gfx::userdata_array const &vulkan14_renderer::UserData(gfx::geometry_handle const &Geometry) const
{
return m_geometry.userdata(Geometry);
}
// materials and their shaders: none until D1
material_handle vulkan14_renderer::Fetch_Material(std::string const &Filename, bool const Loadnow)
{
return null_handle;
}
void vulkan14_renderer::Bind_Material(material_handle const Material, TSubModel const *sm, lighting_data const *lighting) {}
IMaterial const *vulkan14_renderer::Material(material_handle const Material) const
{
return IMaterial::null_material();
}
std::shared_ptr<gfx::shader_program> vulkan14_renderer::Fetch_Shader(std::string const &name)
{
return nullptr;
}
// textures: the files read and decoded as for the opengl renderer, made into images when they are first used (create(), e.g. by the
// launcher); the materials of the scene take them from D1
texture_handle vulkan14_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint)
{
if (!m_context || !m_context->textures)
{
return null_handle;
}
return m_context->textures->create(Filename, Loadnow, format_hint);
}
// the shaders take the textures by the pushed descriptors (D1), there's nothing to bind
void vulkan14_renderer::Bind_Texture(texture_handle const Texture) {}
void vulkan14_renderer::Bind_Texture(std::size_t const Unit, texture_handle const Texture) {}
ITexture &vulkan14_renderer::Texture(texture_handle const Texture)
{
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->mark_as_used(Texture);
}
ITexture const &vulkan14_renderer::Texture(texture_handle const Texture) const
{
if ((Texture == null_handle) || !m_context || !m_context->textures)
{
return *ITexture::null_texture();
}
return m_context->textures->texture(Texture);
}
// picking: D6
void vulkan14_renderer::Pick_Control_Callback(std::function<void(TSubModel const *, glm::vec2 const)> Callback) {}
void vulkan14_renderer::Pick_Node_Callback(std::function<void(scene::basic_node *)> Callback) {}
TSubModel const *vulkan14_renderer::Pick_Control() const
{
return nullptr;
}
scene::basic_node const *vulkan14_renderer::Pick_Node() const
{
return nullptr;
}
glm::dvec3 vulkan14_renderer::Mouse_Position() const
{
return glm::dvec3(0.0);
}
// maintenance
void vulkan14_renderer::Update(double const Deltatime)
{
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
{
// textures nothing used for a while give their images back
m_context->textures->update();
}
Update_Pick_Control();
Update_Pick_Node();
update_screenshot();
m_updateaccumulator += Deltatime;
if (m_updateaccumulator < 1.0)
{
// too early for any work
return;
}
m_updateaccumulator = 0.0;
m_framerate = 1000.f / Timer::subsystem.mainloop_total.average();
// adjust draw ranges etc, based on recent performance (opengl46_renderer::Update())
if (Global.targetfps != 0.0f)
{
float fps_diff = Global.targetfps - m_framerate;
if (fps_diff > 0.0f)
Global.fDistanceFactor = std::max(0.5f, Global.fDistanceFactor - 0.05f);
else
Global.fDistanceFactor = std::min(Global.gfx_distance_factor_max, Global.fDistanceFactor + 0.05f);
}
// legacy framerate parameters
else if (Global.fFpsAverage != 0.f)
{
auto const fps_diff = m_framerate - Global.fFpsAverage;
if (fps_diff < -Global.fFpsDeviation)
{
Global.fDistanceFactor = std::min(std::max(1.0f, Global.fDistanceFactor - 0.05f), Global.gfx_distance_factor_max);
}
else if (fps_diff > Global.fFpsDeviation)
{
Global.fDistanceFactor = std::min(std::min(3.0f, Global.fDistanceFactor + 0.05f), Global.gfx_distance_factor_max);
}
}
if (Global.UpdateMaterials)
{
// update resources if there was environmental change
simulation_state simulationstate{Global.Weather, Global.Season};
std::swap(m_simulationstate, simulationstate);
if ((m_simulationstate.season != simulationstate.season) && (false == simulationstate.season.empty()))
{
m_materials.on_season_change();
}
if ((m_simulationstate.weather != simulationstate.weather) && (false == simulationstate.weather.empty()))
{
m_materials.on_weather_change();
}
}
if (m_context && m_context->textures && (true == Global.ResourceSweep) && (true == simulation::is_ready))
{
// garbage collection
m_geometry.update();
m_context->textures->update();
}
if ((Global.gfx_texture_releasedistance > 0.f) && (true == simulation::is_ready))
{
update_texture_release();
}
update_info();
}
@@ -781,15 +524,6 @@ void vulkan14_renderer::update_info()
}
}
void vulkan14_renderer::Update_Pick_Control() {}
void vulkan14_renderer::Update_Pick_Node() {}
glm::dvec3 vulkan14_renderer::Update_Mouse_Position()
{
return glm::dvec3(0.0);
}
bool vulkan14_renderer::Debug_Ui_State(std::optional<bool> State)
{
if (State)
@@ -813,8 +547,3 @@ imgui_renderer *vulkan14_renderer::GetImguiRenderer()
{
return (m_context ? &m_context->imgui : nullptr);
}
void vulkan14_renderer::MakeScreenshot()
{
vk14::log_warning("screenshots aren't made by the vulkan renderer yet (D6)");
}

View File

@@ -7,11 +7,12 @@ obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan. so far the core of the renderer (the track R of the plan,
// claude/vulkan14-renderer-plan.md): the gpu, the windows with their swapchains, the frames in flight, the memory and the
// uploads, the shaders and the pipelines, the pushed descriptors, the passes with their barriers and gpu times, the textures
// and the user interface (imgui) with the launcher. the scenery and the materials come in the later stages of the plan; until
// then those methods keep the data the simulation reads back and draw nothing.
// the vulkan 1.4 renderer, picked with gfxrenderer vulkan (claude/vulkan14-renderer-plan.md): the gpu, the windows with their
// swapchains, the frames in flight, the memory and the uploads, the shaders and the pipelines, the pushed descriptors, the passes
// with their barriers and gpu times, the textures and the user interface (imgui) with the launcher (the track R of the plan), and
// the scene (the track D): the geometry, the materials, the passes of the colour of the scene with the sun shadows, the
// environment map and the post-processing, the picking, the screenshots, the extra viewports and the light clusters. the scene
// is walked by the walk shared with the opengl renderer (gfx::scene_walk), whose draws this renderer records.
//
// the vulkan headers stay in the files of rendering/vulkan14: the objects of the api live in vulkan14_renderer::context
@@ -19,8 +20,24 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/renderer.h"
#include "rendering/geometrybank.h"
#include "rendering/light.h"
#include "rendering/lightarray.h"
#include "rendering/common/scenewalk.h"
#include "rendering/common/locallights.h"
#include "rendering/particles.h"
#include "model/material.h"
#include "vehicle/Camera.h"
#include "gl/ubo.h"
class vulkan14_renderer : public gfx_renderer
class world_environment;
namespace vk14
{
struct scene_program;
struct target_formats;
enum class blend_mode;
} // namespace vk14
class vulkan14_renderer : public gfx_renderer, public gfx::scene_walk
{
public:
vulkan14_renderer();
@@ -38,16 +55,23 @@ class vulkan14_renderer : public gfx_renderer
gfx::geometry_handle Insert(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
gfx::geometry_handle Insert(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometrybank_handle const &Geometry, int const Type) override;
bool Replace(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type, std::size_t const Offset = 0) override;
bool Replace_Indexed(gfx::index_array &Indices, gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry) override;
bool Append(gfx::vertex_array &Vertices, gfx::userdata_array &Userdata, gfx::geometry_handle const &Geometry, int const Type) override;
gfx::index_array const &Indices(gfx::geometry_handle const &Geometry) const override;
gfx::vertex_array const &Vertices(gfx::geometry_handle const &Geometry) const override;
gfx::userdata_array const &UserData(gfx::geometry_handle const &Geometry) const override;
int Primitive(gfx::geometry_handle const &Geometry) const override;
// materials
material_handle Fetch_Material(std::string const &Filename, bool const Loadnow = true) override;
material_handle Matte_Material(material_handle const Material) override;
material_handle Tinted_Material(material_handle const Material, glm::vec3 const &Colour) override;
material_handle Blend_Material(material_handle const Material, std::array<material_handle, 3> const &Others) override;
bool Blendable_Material(material_handle const Material) override;
void Bind_Material(material_handle const Material, TSubModel const *sm = nullptr, lighting_data const *lighting = nullptr) override;
IMaterial const *Material(material_handle const Material) const override;
// shaders
std::shared_ptr<gfx::shader_program> Fetch_Shader(std::string const &name) override;
std::string Shader_Name(std::shared_ptr<gfx::shader_program> const &Program) const override;
// textures
texture_handle Fetch_Texture(std::string const &Filename, bool const Loadnow = true, GLint format_hint = GL_SRGB_ALPHA) override;
void Bind_Texture(texture_handle const Texture) override;
@@ -60,6 +84,10 @@ class vulkan14_renderer : public gfx_renderer
TSubModel const *Pick_Control() const override;
scene::basic_node const *Pick_Node() const override;
glm::dvec3 Mouse_Position() const override;
bool Mouse_Hit() const override;
glm::mat4 Camera_View_Matrix() const override;
glm::mat4 Camera_Projection_Matrix() const override;
glm::dvec3 Camera_Position() const override;
// maintenance
void Update(double const Deltatime) override;
void Update_Pick_Control() override;
@@ -72,27 +100,203 @@ class vulkan14_renderer : public gfx_renderer
// ui
imgui_renderer *GetImguiRenderer() override;
void MakeScreenshot() override;
bool Render_Preview(TAnimModel *Instance, int const Size, int const Margin, bool const Shadows, std::vector<std::uint8_t> &Image) override;
static std::unique_ptr<gfx_renderer> create_func();
// how the application makes the windows of the renderer, and whether it can run on the machine
static gfx_backend_desc backend_desc();
private:
// the objects of the vulkan api (vulkan14renderer.cpp)
// the objects of the vulkan api (vulkan14context.h)
struct context;
void update_info();
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);
// a viewport the scene is drawn to: the main window, an extra viewport (a window of its own or the main window drawn with
// another projection)
struct viewport_config
{
int width{0};
int height{0};
float draw_range{1.f};
bool main{false};
bool shadow{false}; // its view is taken into the sun shadows
std::size_t window{0}; // the swapchain it goes to (vulkan14_renderer::context::windows)
enum vp_type
{
normal,
custom
} proj_type{normal};
viewport_proj_config projection{};
};
// the sun of the frame, with the factor of the place drawn (tunnels)
struct sun_light : public basic_light
{
float factor{1.f};
};
using gfx::scene_walk::Render;
using gfx::scene_walk::Render_Alpha;
// the draws of the shared walk of the scene, recorded
void walk_material(material_handle const Material, TSubModel const *Submodel = nullptr, lighting_data const *Lighting = nullptr) override;
void walk_shadow_material(material_handle const Material) override;
opengl_material const &walk_material_data(material_handle const Material) const override;
opengl_material const &walk_material_data(TSubModel const *Submodel) const override;
void walk_program(walk_shader const Program) override;
void walk_texture(std::size_t const Unit, texture_handle const Texture) override;
void walk_blending(bool const Alpha) override;
void walk_sunlight(float const Factor = 1.f) override;
void walk_environment(TEnvironmentType const Environment = e_flat) override;
void walk_ambient_save() override;
void walk_ambient_add(glm::vec3 const &Addition) override;
void walk_ambient_restore() override;
glm::vec4 walk_sun_ambient() const override;
void walk_draw(gfx::geometry_handle const &Geometry) override;
void walk_draw(std::vector<gfx::geometrybank_handle>::iterator First, std::vector<gfx::geometrybank_handle>::iterator Last) override;
std::uint32_t walk_instances(glm::mat4 const *Modelviews, std::size_t const Count) override;
void walk_cull_face(bool const Enabled) override;
void walk_glare(bool const Enabled) override;
void walk_glare_draw(TSubModel *Submodel, gfx::geometry_handle const &Geometry) override;
void walk_light_query(TSubModel *Submodel, bool const Begin) override;
void walk_editor_cursor() override;
// the scene (vulkan14scene.cpp)
bool init_scene(std::string &Reason);
// what the renderer prepares for the frames once it's there: the viewport of the main window, the common textures and geometry
void init_frame_data();
void destroy_scene();
// the scene of the frame: the passes of the viewports
void render_scene();
void setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false);
void setup_matrices();
// the colour of a viewport, with the passes it needs first: the sun shadows, the environment map, the light clusters
void render_viewport(std::size_t const Viewport);
void render_shadows(viewport_config &Viewport);
bool render_reflections(viewport_config &Viewport);
void render_reflection_face(viewport_config &Viewport);
void render_picking(rendermode const Mode);
// the sky dome and the sun and the moon
void render_environment(world_environment *Environment);
void render_particles();
void render_precipitation();
void render_editor_overlay();
// the post-processing of a viewport: the motion blur, the tonemapping, the chromatic aberration
void render_post(std::size_t const Viewport, bool const Motionblur);
// the output of a viewport to its window, with the user interface over it for the main window
void present_viewport(std::size_t const Viewport);
// the light buffer of the pass: the sun, the ambient light, the fog, the headlights, the lamps (the clustered lights)
void update_lights(light_array &Lights, bool const Headlights = true, bool const Clustered = false);
void write_local_light(gl::light_element &Light, gfx::local_lights::light const &Source, glm::dvec3 const &Camera, glm::mat4 const &View);
void headlight_projector(light_array::light_record const &Light, glm::dvec3 &Position, glm::mat3 &Rotation, glm::mat4 &Projection, float &Near, float &Fieldofview);
// the light buffer and its header written for the draws which follow
void upload_light_header();
void upload_lights();
void upload_scene_block();
void setup_sunlight_intensity(float const Factor = 1.f);
void setup_shadow_color(glm::vec4 const &Shadowcolor);
void setup_shadow_matrices();
// the light clusters of the pass: the grid and the lists of the lights
void update_clusters(glm::ivec2 const &Targetsize);
// the materials
material_handle invalid_material() const
{
return m_invalid_material;
}
// the draws: the pipeline of the program for the state of the walk, made when it's first needed
void draw_chunk(gfx::geometry_handle const &Geometry, bool const Newblock);
// a pass of the scene recorded with the walk: its targets, its size, its pass set (vk14::scene_state::pass_sets)
void start_draws(vk14::target_formats const &Targets, int const Width, int const Height, int const Set, bool const Shadows);
void end_draws();
// the rendering of the colour of a viewport, begun with the images cleared or again where it was
void begin_colour_rendering(std::size_t const Viewport, bool const Clear);
void push_constants(void const *Constants);
// a pipeline of the post-processing (a VkPipeline), nullptr when the shader isn't there. Encode: the colour is encoded by the
// shader (OUTPUT_GAMMA), -1: as gfx.shadergamma says
void *post_pipeline(char const *Fragment, int const Format, int const Encode = -1);
// the screenshot copied with a frame the gpu is done with, saved
void update_screenshot();
void bind_pass_set();
bool bind_scene_pipeline(vk14::scene_program &Program, vk14::target_formats const &Targets, vk14::blend_mode const Blend, int const Topology);
void apply_draw_state();
void push_textures();
std::uint32_t push_draw_block();
void open_instance_window();
void update_texture_release();
std::unique_ptr<context> m_context;
// the data of the geometry, which the simulation reads back; nothing on the gpu yet
// the data of the geometry, which the simulation reads back, and the banks with it on the gpu
gfx::geometrybank_manager m_geometry;
std::vector<gfx::geometry_bank *> m_banks;
material_manager m_materials;
material_handle m_invalid_material{null_handle};
std::vector<viewport_config> m_viewports;
viewport_config *m_current_viewport{nullptr};
renderpass_config m_colorpass; // of the latest colour pass
std::array<renderpass_config, 3> m_shadowpass; // of the latest cascades of the sun shadows
sun_light m_sunlight;
glm::vec3 m_fogcolor{0.f};
glm::vec4 m_shadowcolor{colors::shadow};
gl::scene_ubs scene_ubs{};
gl::light_data light_data{};
std::uint32_t m_lightcount{1}; // lights written to light_data
// the copies of the light buffer in the ring of the frame since the lights were last written, by the header and the sun
std::vector<std::pair<std::array<std::uint8_t, offsetof(gl::light_data, lights) + sizeof(gl::light_element)>, std::uint32_t>> m_lightheaders;
packed_vec3 m_cabambient{0.f};
gfx::local_lights m_locallights;
std::uint32_t m_lights_lit{0};
std::uint32_t m_lights_dropped{0};
// the reflections: the face drawn, and the place and the time of the last update
int m_environmentcubetextureface{0};
int m_environmentupdateface{6};
bool m_environmentready{false};
double m_environmentupdatetime{0.0};
glm::dvec3 m_environmentupdatelocation{0.0};
bool m_vehiclesmoved{true};
texture_handle m_suntexture{-1};
texture_handle m_moontexture{-1};
texture_handle m_smoketexture{-1};
texture_handle m_headlightstexture{-1};
texture_handle m_precipitationtexture{-1};
float m_precipitationovercast{-1.f};
double m_precipitationrotation{0.0};
// the editor overlay
gfx::geometrybank_handle m_editor_overlay_bank{};
std::vector<gfx::geometry_handle> m_editor_overlay_geometry;
unsigned int m_editor_overlay_revision{0};
unsigned int m_nodestateversion{0};
// picking
std::vector<std::function<void(TSubModel const *, glm::vec2)>> m_control_pick_requests;
std::vector<std::function<void(scene::basic_node *)>> m_node_pick_requests;
TSubModel const *m_pickcontrolitem{nullptr};
scene::basic_node *m_picksceneryitem{nullptr};
bool m_pickcontrolswanted{false};
bool m_picknodewanted{false};
glm::ivec2 m_pickposition{0};
glm::vec3 m_worldmousecoordinates{0.f};
bool m_mousehit{false};
// the state of the draws of the walk being recorded
struct draw_state;
std::unique_ptr<draw_state> m_draw;
// textures used by vehicle models, for the release of far textures
std::unordered_map<TModel3d const *, std::vector<texture_handle>> m_modeltextures;
std::vector<texture_handle> const &Model_Textures(TModel3d *Model);
// the season and the weather the materials were last given
struct simulation_state
{
std::string weather;
std::string season;
} m_simulationstate;
float m_framerate{0.f};
double m_updateaccumulator{0.0};
bool m_debug_ui_active{false};
std::string m_infotimes;
std::string m_infostats;
debug_stats m_laststats;
void update_info();
// the device was lost: what was on the gpu goes to the log
void report_device_lost(char const *Where);
static bool renderer_register;
};

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14shaders.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "rendering/vulkan14/vulkan14bindings.h"
#include "gl/ubo.h"
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
@@ -562,6 +564,24 @@ VkPipeline pipeline_library::get(pipeline_desc const &Desc)
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::scene_alpha:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::scene_additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.colorBlendOp = VK_BLEND_OP_ADD;
attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
attachment.alphaBlendOp = VK_BLEND_OP_ADD;
break;
case blend_mode::additive:
attachment.blendEnable = VK_TRUE;
attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
@@ -703,3 +723,60 @@ void set_draw_state(VkCommandBuffer Commands, VkExtent2D Extent)
}
} // namespace vk14
namespace vk14
{
std::string binding_preamble()
{
std::string preamble{"#define MASZYNA_CONSTANTS 1\n"};
auto const define = [&preamble](char const *Name, auto const Value, char const *Suffix = "") { preamble += std::string("#define ") + Name + " " + std::to_string(Value) + Suffix + "\n"; };
using namespace vk14::bindings;
define("UI_SET", UI_SET);
define("UI_TEXTURE", UI_TEXTURE);
define("PASS_SET", PASS_SET);
define("MATERIAL_SET", MATERIAL_SET);
define("SCENE_UBO", SCENE_UBO);
define("DRAW_SSBO", DRAW_SSBO);
define("INSTANCE_SSBO", INSTANCE_SSBO);
define("LIGHT_SSBO", LIGHT_SSBO);
define("CLUSTER_UBO", CLUSTER_UBO);
define("CLUSTER_RANGE_SSBO", CLUSTER_RANGE_SSBO);
define("CLUSTER_INDEX_SSBO", CLUSTER_INDEX_SSBO);
define("SHADOW_TEX", SHADOW_TEX);
define("ENV_TEX", ENV_TEX);
define("HEADLIGHT_TEX", HEADLIGHT_TEX);
define("PASS_BINDING_COUNT", PASS_BINDING_COUNT);
define("MATERIAL_TEXTURE_FIRST", MATERIAL_TEXTURE_FIRST);
define("CL_CLUSTER_UBO", CL_CLUSTER_UBO);
define("CL_AABB_SSBO", CL_AABB_SSBO);
define("CL_RANGE_SSBO", CL_RANGE_SSBO);
define("CL_INDEX_SSBO", CL_INDEX_SSBO);
define("CL_COUNTER_SSBO", CL_COUNTER_SSBO);
define("CL_LIGHT_SSBO", CL_LIGHT_SSBO);
define("POST_SET", POST_SET);
define("POST_TEXTURE_FIRST", POST_TEXTURE_FIRST);
define("PUSH_CONSTANTS_SIZE", PUSH_CONSTANTS_SIZE);
define("SPEC_OUTPUT_GAMMA", SPEC_OUTPUT_GAMMA);
define("SPEC_SHADOWMAP_ENABLED", SPEC_SHADOWMAP_ENABLED);
define("SPEC_ENVMAP_ENABLED", SPEC_ENVMAP_ENABLED);
define("SPEC_MOTIONBLUR_ENABLED", SPEC_MOTIONBLUR_ENABLED);
define("SPEC_POSTFX_ENABLED", SPEC_POSTFX_ENABLED);
define("SPEC_EXTRAEFFECTS_ENABLED", SPEC_EXTRAEFFECTS_ENABLED);
define("SPEC_COUNT", SPEC_COUNT);
// the constants of the game the blocks of the shaders are sized by, the same as for the opengl renderer (gl/ubo.h)
define("MAX_LIGHTS", static_cast<unsigned int>(gl::MAX_LIGHTS), "U");
define("MAX_CASCADES", static_cast<unsigned int>(gl::MAX_CASCADES), "U");
define("MAX_PARAMS", static_cast<unsigned int>(gl::MAX_PARAMS), "U");
define("MAX_HEADLIGHT_SHADOWS", static_cast<unsigned int>(gl::MAX_HEADLIGHT_SHADOWS), "U");
define("CLUSTER_GRID_X", gl::CLUSTER_GRID_X);
define("CLUSTER_GRID_Y", gl::CLUSTER_GRID_Y);
define("CLUSTER_GRID_Z", gl::CLUSTER_GRID_Z);
define("CLUSTER_COUNT", gl::CLUSTER_COUNT);
define("MAX_LIGHTS_PER_CLUSTER", gl::MAX_LIGHTS_PER_CLUSTER);
define("CLUSTER_BUILD_GROUP_SIZE", gl::CLUSTER_BUILD_GROUP_SIZE);
define("CLUSTER_CULL_GROUP_SIZE", gl::CLUSTER_CULL_GROUP_SIZE);
return preamble;
}
} // namespace vk14

View File

@@ -76,7 +76,10 @@ enum class blend_mode
none,
alpha, // the colour over what is there by its alpha, the alpha kept for what's behind (the user interface)
premultiplied,
additive
additive,
// the blending of the scene, as glBlendFunc() of the opengl renderer gives it to the colour and the alpha alike
scene_alpha, // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA
scene_additive // GL_SRC_ALPHA, GL_ONE (the glare of the lamps)
};
struct vertex_layout

View File

@@ -57,10 +57,59 @@ void uploader::begin_frame(VkCommandBuffer Commands, std::uint64_t Value)
bool uploader::end_frame()
{
if (m_buffercopies && (m_commands != VK_NULL_HANDLE))
{
// the draws of the frame read what was copied
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
barrier.dstAccessMask = VK_ACCESS_2_MEMORY_READ_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(m_commands, &dependency);
}
m_buffercopies = false;
m_commands = VK_NULL_HANDLE;
return m_recorded;
}
bool uploader::upload_buffer(std::shared_ptr<buffer> const &Target, void const *Data, VkDeviceSize Size, std::vector<std::pair<VkDeviceSize, VkDeviceSize>> const &Parts)
{
if ((Target == nullptr) || (Size == 0))
{
return true;
}
job upload;
upload.buffertarget = Target;
upload.parts = Parts;
if (upload.parts.empty())
{
upload.parts.emplace_back(0, Size);
}
upload.staging = std::make_shared<buffer>();
if ((false == upload.staging->create(*m_allocator, Size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, memory_kind::upload)) || (upload.staging->mapped() == nullptr))
{
log_error("no staging buffer of " + memory_size_string(Size) + " for an upload");
return false;
}
std::memcpy(upload.staging->mapped(), Data, static_cast<std::size_t>(Size));
upload.staging->flush(0, Size);
m_uploaded += Size;
if (m_commands != VK_NULL_HANDLE)
{
record(upload);
}
else
{
m_pending.emplace_back(std::move(upload));
}
return true;
}
bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps)
{
job upload;
@@ -89,6 +138,47 @@ bool uploader::upload(std::shared_ptr<image> const &Image, void const *Data, VkD
void uploader::record(job &Job)
{
if (Job.buffertarget != nullptr)
{
if (false == m_buffercopies)
{
// the frames sent before may still read the buffers the copies write to
VkMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.memoryBarrierCount = 1;
dependency.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(m_commands, &dependency);
m_buffercopies = true;
}
std::vector<VkBufferCopy2> regions;
VkDeviceSize source{0};
for (auto const &part : Job.parts)
{
VkBufferCopy2 region{};
region.sType = VK_STRUCTURE_TYPE_BUFFER_COPY_2;
region.srcOffset = source;
region.dstOffset = part.first;
region.size = part.second;
regions.push_back(region);
source += part.second;
}
VkCopyBufferInfo2 copy{};
copy.sType = VK_STRUCTURE_TYPE_COPY_BUFFER_INFO_2;
copy.srcBuffer = Job.staging->handle();
copy.dstBuffer = Job.buffertarget->handle();
copy.regionCount = static_cast<std::uint32_t>(regions.size());
copy.pRegions = regions.data();
vkCmdCopyBuffer2(m_commands, &copy);
m_recorded = true;
m_deletions->push(m_value, [staging = Job.staging, target = Job.buffertarget]() {});
return;
}
auto &target{*Job.target};
auto const commands{m_commands};
auto const aspect{target.aspect()};

View File

@@ -54,6 +54,10 @@ class uploader
// copies Data into Image (the levels given, the others made by blits of the first one when Mipmaps), the image left ready to be
// sampled. Data is copied away. false: no staging memory, nothing will be copied
bool upload(std::shared_ptr<image> const &Image, void const *Data, VkDeviceSize Size, std::vector<level> const &Levels, bool const Mipmaps);
// copies Data into Target, done before the draws of the frame read it (and after the frames sent before did). Data holds the
// pieces of Parts (offset in Target, size) one after another; all of Data goes to the start of Target when there are none.
// false: no staging memory
bool upload_buffer(std::shared_ptr<buffer> const &Target, void const *Data, VkDeviceSize Size, std::vector<std::pair<VkDeviceSize, VkDeviceSize>> const &Parts = {});
// the bytes uploaded since the start, for the debug panel
std::uint64_t uploaded() const
{
@@ -69,6 +73,9 @@ class uploader
std::shared_ptr<buffer> staging;
std::vector<level> levels;
bool mipmaps{false};
// a copy to a buffer instead: the pieces copied
std::shared_ptr<buffer> buffertarget;
std::vector<std::pair<VkDeviceSize, VkDeviceSize>> parts;
};
void record(job &Job);
@@ -78,6 +85,8 @@ class uploader
VkCommandBuffer m_commands{VK_NULL_HANDLE};
std::uint64_t m_value{0};
bool m_recorded{false};
// buffers were copied to in the frame: the barriers before the first copy and after the last one
bool m_buffercopies{false};
std::vector<job> m_pending;
std::uint64_t m_uploaded{0};
mutable std::unordered_map<VkFormat, bool> m_blittable;
@@ -153,9 +162,10 @@ struct vulkan14_texture : public texture_source
bool create(bool const Static = false) override;
void release() override;
// as the opengl textures give it: one not made yet has no id of 0, which the materials take for a texture which is missing
size_t get_id() const override
{
return static_cast<size_t>(m_id);
return (m_made ? static_cast<size_t>(m_id) : std::numeric_limits<size_t>::max());
}
void update_from_memory(size_t width, size_t height, const uint8_t *data) override;
bool resident() const override