30constexpr auto kHostVisibleCoherent = vk::MemoryPropertyFlagBits::eHostVisible |
31 vk::MemoryPropertyFlagBits::eHostCoherent;
33template <
typename T,
size_t N>
34std::vector<T> embeddedSpirv(
const T (&words)[N]) {
35 return {words, words + N};
38template <
typename Slots>
39auto ¤tSlot(Slots &slots,
size_t slotCount,
size_t slotIndex) {
40 if (slots.size() != slotCount) slots.resize(slotCount);
41 return slots[slotIndex];
44template <
typename FrameBuffers>
45void releaseFrame2dBuffers(FrameBuffers &buffers) {
46 for (
auto &buffer : buffers.solidBufs) buffer.release();
47 buffers.solidBufs.clear();
48 for (
auto &buffer : buffers.texBufs) buffer.release();
49 buffers.texBufs.clear();
52void setViewportAndScissor(vk::CommandBuffer cb, uint32_t
width, uint32_t
height) {
55 cb.setViewport(0, 1, &viewport);
56 cb.setScissor(0, 1, &scissor);
59vk::PipelineLayout createPipelineLayout(vkb::Device &
device,
60 vk::DescriptorSetLayout setLayout = {},
61 const vk::PushConstantRange *pushConstant =
nullptr) {
62 vk::PipelineLayoutCreateInfo info{};
64 info.setLayoutCount = 1;
65 info.pSetLayouts = &setLayout;
68 info.pushConstantRangeCount = 1;
69 info.pPushConstantRanges = pushConstant;
71 return device->createPipelineLayout(info);
74vk::PushConstantRange pushConstantRange(vk::ShaderStageFlags stages, uint32_t size) {
75 vk::PushConstantRange range{};
76 range.stageFlags = stages;
81void destroySampler(vkb::Device &
device, vk::Sampler &sampler) {
83 device->destroySampler(sampler);
84 sampler = vk::Sampler{};
87void destroyPipeline(vkb::Device &
device, vk::Pipeline &pipeline) {
88 if (!pipeline)
return;
89 device->destroyPipeline(pipeline);
90 pipeline = vk::Pipeline{};
93void destroyPipelineLayout(vkb::Device &
device, vk::PipelineLayout &
layout) {
96 layout = vk::PipelineLayout{};
101vkb::HostVertexBuffer &meshDrawVertices(GpuMesh &
mesh) {
102 if (!
mesh.dynamic)
return mesh.vertices;
104 return mesh.dynVertices[slot];
107vkb::GenericBuffer &meshDrawIndices(GpuMesh &
mesh) {
108 if (!
mesh.dynamic)
return mesh.indices;
110 return mesh.dynIndices[slot];
113void drawIndexedMesh(vk::CommandBuffer cb, GpuMesh &
mesh) {
114 const vk::DeviceSize offset = 0;
115 cb.bindVertexBuffers(0, 1, meshDrawVertices(
mesh), &offset);
116 cb.bindIndexBuffer(meshDrawIndices(
mesh).buffer, 0,
mesh.indexType);
117 cb.drawIndexed(
mesh.indexCount, 1, 0, 0, 0);
123void ensureDynamicRing(GpuMesh &gpu) {
124 if (gpu.dynamic)
return;
126 gpu.dynamicWriteCount = 0;
127 gpu.cpuIndices.resize(gpu.indexCount);
128 if (gpu.indexCount > 0 && gpu.indices.buffer) {
129 void *
ptr = gpu.indices.map();
130 if (gpu.indexType == vk::IndexType::eUint16) {
131 const auto *src =
static_cast<const uint16_t *
>(
ptr);
132 for (uint32_t i = 0; i < gpu.indexCount; ++i)
133 gpu.cpuIndices[
size_t(i)] = src[i];
135 std::memcpy(gpu.cpuIndices.data(),
ptr,
136 size_t(gpu.indexCount) *
sizeof(uint32_t));
140 gpu.indexType = vk::IndexType::eUint32;
145void writeDynamicMesh(GpuMesh &gpu,
const std::vector<MeshVertex> &
verts, vkb::Device &
device,
146 vkb::FrameSlot frame,
const uint32_t *indices,
int indexCount) {
148 gpu.dynVertices[slot].allocate<MeshVertex>(frame,
device,
verts);
149 if (indices && indexCount > 0) {
150 gpu.cpuIndices.assign(indices, indices + indexCount);
151 gpu.indexCount = uint32_t(indexCount);
153 if (!gpu.cpuIndices.empty()) {
154 auto &ib = gpu.dynIndices[slot];
155 ib.allocate(frame,
device, vk::BufferUsageFlagBits::eIndexBuffer,
156 vk::DeviceSize(gpu.cpuIndices.size()) *
sizeof(uint32_t),
157 kHostVisibleCoherent);
158 ib.updateLocal(frame, gpu.cpuIndices.data(),
159 vk::DeviceSize(gpu.cpuIndices.size()) *
sizeof(uint32_t));
161 ++gpu.dynamicWriteCount;
164std::unique_ptr<GpuMesh> uploadGpuMesh(vkb::Device &
device, vkb::FrameSlot frame,
165 const std::vector<MeshVertex> &vertices,
166 const std::vector<uint32_t> &indices) {
167 auto gpu = std::make_unique<GpuMesh>();
168 gpu->vertices.allocate<MeshVertex>(frame,
device, vertices);
169 gpu->indices.allocate(frame,
device, vk::BufferUsageFlagBits::eIndexBuffer,
170 indices.size() *
sizeof(uint32_t), kHostVisibleCoherent);
171 gpu->indices.updateLocal(frame, indices.data(), indices.size() *
sizeof(uint32_t));
172 gpu->indexCount = uint32_t(indices.size());
177std::unique_ptr<GpuMesh> uploadGpuMesh16(vkb::Device &
device, vkb::FrameSlot frame,
178 const std::vector<MeshVertex> &vertices,
179 const std::vector<uint16_t> &indices) {
180 auto gpu = std::make_unique<GpuMesh>();
181 gpu->vertices.allocate<MeshVertex>(frame,
device, vertices);
182 gpu->indices.allocate(frame,
device, vk::BufferUsageFlagBits::eIndexBuffer,
183 indices.size() *
sizeof(uint16_t), kHostVisibleCoherent);
184 gpu->indices.updateLocal(frame, indices.data(), indices.size() *
sizeof(uint16_t));
185 gpu->indexCount = uint32_t(indices.size());
186 gpu->indexType = vk::IndexType::eUint16;
190std::unique_ptr<Mesh> makeMeshHandle(GpuMesh &gpu) {
191 auto mesh = std::make_unique<Mesh>();
192 mesh->indexCount = int(gpu.indexCount);
193 mesh->gpuHandle = &gpu;
197void assignMeshBounds(Mesh *
mesh,
const std::vector<MeshVertex> &
verts) {
202 mesh->boundsCx =
c.x;
203 mesh->boundsCy =
c.y;
204 mesh->boundsCz =
c.z;
206 for (
const auto &
v :
verts) {
207 const glm::vec3
d =
v.pos -
c;
208 const float len = std::sqrt(
d.x *
d.x +
d.y *
d.y +
d.z *
d.z);
209 if (len > r) r = len;
213 mesh->boundsRadius = r > 0.f ? r : 1e-4f;
217void updateRingLocal(vkb::GenericBuffer &ring, vk::DeviceSize byteOffset,
const void *data,
218 vk::DeviceSize bytes) {
219 if (!ring.buffer || !data || bytes == 0)
return;
220 void *
ptr = ring.map();
221 std::memcpy(
static_cast<char *
>(
ptr) + byteOffset, data,
size_t(bytes));
226inline T alignUpValue(T
value, T align) {
227 return align > 0 ? (
value + align - 1) / align * align :
value;
230vk::PipelineColorBlendAttachmentState makeBlendAttachment(
BlendMode mode) {
231 vk::PipelineColorBlendAttachmentState att{};
233 vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
234 vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
235 att.blendEnable =
true;
237 att.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha;
238 att.dstColorBlendFactor = vk::BlendFactor::eOne;
239 att.colorBlendOp = vk::BlendOp::eAdd;
240 att.srcAlphaBlendFactor = vk::BlendFactor::eOne;
241 att.dstAlphaBlendFactor = vk::BlendFactor::eOne;
242 att.alphaBlendOp = vk::BlendOp::eAdd;
244 att.srcColorBlendFactor = vk::BlendFactor::eSrcAlpha;
245 att.dstColorBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
246 att.colorBlendOp = vk::BlendOp::eAdd;
247 att.srcAlphaBlendFactor = vk::BlendFactor::eOne;
248 att.dstAlphaBlendFactor = vk::BlendFactor::eOneMinusSrcAlpha;
249 att.alphaBlendOp = vk::BlendOp::eAdd;
254class ShaderModulePair {
256 ShaderModulePair(vkb::Device &device,
const std::vector<uint32_t> &vert,
257 const std::vector<uint32_t> &frag)
259 vert(vkb::PipelineBuilder::createShaderModule(
device.instance,
vert)),
260 frag(vkb::PipelineBuilder::createShaderModule(
device.instance,
frag)) {}
262 ~ShaderModulePair() {
263 device->destroyShaderModule(vert);
264 device->destroyShaderModule(frag);
267 ShaderModulePair(
const ShaderModulePair &) =
delete;
268 ShaderModulePair &operator=(
const ShaderModulePair &) =
delete;
277vk::Format pickGBufferColorFormat(vkb::Device &
device) {
279 return vk::Format::eR8G8B8A8Unorm;
282vk::SampleCountFlagBits sampleCountFlagFor(
int samples) {
283 if (samples >= 8)
return vk::SampleCountFlagBits::e8;
284 if (samples >= 4)
return vk::SampleCountFlagBits::e4;
285 if (samples >= 2)
return vk::SampleCountFlagBits::e2;
286 return vk::SampleCountFlagBits::e1;
291uint32_t rgba8MipBytes(uint32_t
width, uint32_t
height) {
296void appendBoxFilteredMip(std::vector<uint8_t> &out,
const uint8_t *src, uint32_t srcW,
297 uint32_t srcH, uint32_t dstW, uint32_t dstH) {
298 const size_t base = out.size();
299 out.resize(base +
size_t(rgba8MipBytes(dstW, dstH)));
300 uint8_t *dst = out.data() + base;
301 for (uint32_t
y = 0;
y < dstH; ++
y) {
302 const uint32_t y0 = std::min(
y * 2u, srcH - 1u);
303 const uint32_t y1 = std::min(y0 + 1u, srcH - 1u);
304 for (uint32_t
x = 0;
x < dstW; ++
x) {
305 const uint32_t x0 = std::min(
x * 2u, srcW - 1u);
306 const uint32_t x1 = std::min(x0 + 1u, srcW - 1u);
307 uint32_t acc[4] = {0, 0, 0, 0};
308 const uint32_t samples[4][2] = {{x0, y0}, {x1, y0}, {x0, y1}, {x1, y1}};
309 for (
const auto &
s : samples) {
310 const size_t i = (size_t(
s[1]) * srcW +
s[0]) * 4u;
311 acc[0] += src[i + 0];
312 acc[1] += src[i + 1];
313 acc[2] += src[i + 2];
314 acc[3] += src[i + 3];
316 const size_t o = (size_t(
y) * dstW +
x) * 4u;
317 dst[o + 0] =
static_cast<uint8_t
>((acc[0] + 2u) / 4u);
318 dst[o + 1] =
static_cast<uint8_t
>((acc[1] + 2u) / 4u);
319 dst[o + 2] =
static_cast<uint8_t
>((acc[2] + 2u) / 4u);
320 dst[o + 3] =
static_cast<uint8_t
>((acc[3] + 2u) / 4u);
326std::vector<uint8_t> buildMipChain2D(
const uint8_t *rgba, uint32_t
width, uint32_t
height,
327 uint32_t mipLevels) {
328 std::vector<uint8_t> packed;
329 packed.reserve(
size_t(
width) *
size_t(
height) * 4u * 2u);
330 packed.insert(packed.end(), rgba, rgba +
size_t(rgba8MipBytes(
width,
height)));
332 uint32_t srcW =
width;
334 size_t srcOffset = 0;
335 for (uint32_t level = 1; level < mipLevels; ++level) {
336 const uint32_t dstW = std::max(srcW >> 1, 1u);
337 const uint32_t dstH = std::max(srcH >> 1, 1u);
338 const uint8_t *src = packed.data() + srcOffset;
339 srcOffset = packed.size();
340 appendBoxFilteredMip(packed, src, srcW, srcH, dstW, dstH);
351std::vector<uint8_t> buildMipChainCube(
const uint8_t *rgbaFaces, uint32_t faceSize,
352 uint32_t mipLevels) {
353 const uint32_t faceBytes = rgba8MipBytes(faceSize, faceSize);
354 std::vector<std::vector<uint8_t>> faceChains(6);
355 for (uint32_t
f = 0;
f < 6; ++
f) {
356 faceChains[
f] = buildMipChain2D(rgbaFaces +
size_t(
f) * faceBytes, faceSize, faceSize,
360 std::vector<uint8_t> packed;
361 uint32_t
w = faceSize;
362 uint32_t
h = faceSize;
363 size_t faceOffsets[6] = {0, 0, 0, 0, 0, 0};
364 for (uint32_t level = 0; level < mipLevels; ++level) {
365 const uint32_t levelBytes = rgba8MipBytes(
w,
h);
366 for (uint32_t
f = 0;
f < 6; ++
f) {
367 const uint8_t *src = faceChains[
f].data() + faceOffsets[
f];
368 packed.insert(packed.end(), src, src + levelBytes);
369 faceOffsets[
f] += levelBytes;
371 w = std::max(
w >> 1, 1u);
372 h = std::max(
h >> 1, 1u);
377TextureCreateInfo normalizeTextureInfo(TextureCreateInfo info) {
380 if (info.sampler.maxAnisotropy < 1.f) info.sampler.maxAnisotropy = 1.f;
386std::string normalizeTexPath(std::string path) {
387 for (
char &
c : path) {
388 if (
c ==
'\\')
c =
'/';
390 while (path.size() >= 2 && path[0] ==
'.' && path[1] ==
'/') path.erase(0, 2);
391 while (path.size() > 1 && path.back() ==
'/') path.pop_back();
397std::vector<uint32_t> loadSpirvBytes(
const void *data,
size_t size) {
398 if (!data || size < 4 || (size % 4) != 0)
399 throw Exception(
"SPIR-V: invalid size %zu", size);
400 const auto *words =
static_cast<const uint32_t *
>(
data);
401 if (words[0] != 0x07230203)
402 throw Exception(
"SPIR-V: bad magic (expected 0x07230203)");
403 return std::vector<uint32_t>(words, words + size / 4);
406std::vector<uint32_t> readSpirvFile(
const std::string &path) {
407 auto *fs = filesystem::Filesystem::create();
408 std::unique_ptr<filesystem::FileData> fd(fs->read(path));
409 if (!fd)
throw Exception(
"newShaderFromSpvFile: failed to read '%s'", path.c_str());
410 return loadSpirvBytes(fd->getData(), fd->getSize());
413std::vector<uint32_t> compileGlslWithGlslc(
const std::string &source,
const char *stage) {
414 if (source.empty())
throw Exception(
"newShader: empty %s GLSL", stage);
418 throw Exception(
"newShader: GLSL compile via glslc is not supported on Windows; "
419 "use newShaderFromSpv / newShaderFromSpvFile");
421 char inPath[] =
"/tmp/eve_shader_XXXXXX";
422 int fd = mkstemp(inPath);
423 if (fd < 0)
throw Exception(
"newShader: mkstemp failed");
424 std::string outPath = std::string(inPath) +
".spv";
426 ssize_t
n = write(fd, source.data(), source.size());
428 if (
n < 0 ||
size_t(
n) != source.size()) {
430 throw Exception(
"newShader: failed to write temp GLSL");
434 std::string cmd = std::string(
"glslc -fshader-stage=") + stage +
" \"" + inPath +
"\" -o \"" +
436 FILE *pipe = popen(cmd.c_str(),
"r");
440 while (fgets(buf,
sizeof(buf), pipe)) err += buf;
441 int status = pclose(pipe);
444 unlink(outPath.c_str());
445 throw Exception(
"newShader: glslc failed for %s:\n%s", stage, err.c_str());
449 throw Exception(
"newShader: glslc not available (popen failed)");
452 FILE *
f = fopen(outPath.c_str(),
"rb");
454 unlink(outPath.c_str());
455 throw Exception(
"newShader: failed to open compiled SPIR-V");
457 fseek(
f, 0, SEEK_END);
459 fseek(
f, 0, SEEK_SET);
460 std::vector<uint8_t> bytes(
static_cast<size_t>(sz > 0 ? sz : 0));
461 if (sz > 0 && fread(bytes.data(), 1,
static_cast<size_t>(sz),
f) !=
static_cast<size_t>(sz)) {
463 unlink(outPath.c_str());
464 throw Exception(
"newShader: failed to read compiled SPIR-V");
467 unlink(outPath.c_str());
468 return loadSpirvBytes(bytes.data(), bytes.size());
std::vector< std::uint32_t > verts
BlendMode
2D quad blend mode (drawn in draw order within a layer).
WidgetDesc viewport(std::string id, float width, float height)
static constexpr size_t kDynamicVertexCopies
Ring copies for per-frame updated meshes (skin/morph/sprite stack). Writing the next copy never races...