react-native-cesium 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +24 -9
- package/android/src/main/cpp/vulkan/VulkanBackend.cpp +166 -67
- package/android/src/main/cpp/vulkan/VulkanBackend.h +60 -19
- package/android/src/main/cpp/vulkan/shaders/terrain.frag +36 -8
- package/android/src/main/java/com/margelo/nitro/reactnativecesium/HybridCesiumView.kt +14 -1
- package/cpp/engine/CameraIntegrator.cpp +32 -10
- package/cpp/engine/CameraIntegrator.hpp +1 -1
- package/cpp/engine/CesiumEngine.cpp +487 -34
- package/cpp/engine/CesiumEngine.hpp +73 -4
- package/cpp/engine/EngineTunables.hpp +7 -0
- package/cpp/renderer/RenderTypes.hpp +13 -0
- package/ios/CesiumBridge.h +9 -3
- package/ios/CesiumBridge.mm +60 -5
- package/ios/HybridCesiumView.swift +25 -8
- package/ios/metal/MetalBackend.h +22 -2
- package/ios/metal/MetalBackend.mm +248 -93
- package/package.json +1 -1
package/android/CMakeLists.txt
CHANGED
|
@@ -24,24 +24,39 @@ set(SHADER_DIR "${CMAKE_SOURCE_DIR}/src/main/cpp/vulkan/shaders")
|
|
|
24
24
|
set(SPIRV_OUTPUT_DIR "${CMAKE_BINARY_DIR}/shaders")
|
|
25
25
|
file(MAKE_DIRECTORY "${SPIRV_OUTPUT_DIR}")
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
# Each entry: "SOURCE|OUTPUT_BASENAME|GLSLC_DEFINES"
|
|
28
|
+
# terrain.frag is built TWICE — once plain (solid pipeline, no discard so
|
|
29
|
+
# early-Z stays enabled) and once with TERRAIN_DITHER=1 (dither pipeline used
|
|
30
|
+
# only for tiles actively crossing an LOD transition). See VulkanBackend
|
|
31
|
+
# createTerrain{Solid,Dither}Pipeline for how the two SPV blobs are consumed.
|
|
32
|
+
set(SHADER_BUILDS
|
|
33
|
+
"${SHADER_DIR}/terrain.vert|terrain.vert|"
|
|
34
|
+
"${SHADER_DIR}/terrain.frag|terrain.frag|"
|
|
35
|
+
"${SHADER_DIR}/terrain.frag|terrain_dither.frag|TERRAIN_DITHER=1"
|
|
36
|
+
"${SHADER_DIR}/sky.vert|sky.vert|"
|
|
37
|
+
"${SHADER_DIR}/sky.frag|sky.frag|"
|
|
32
38
|
)
|
|
33
39
|
|
|
34
|
-
foreach(
|
|
35
|
-
|
|
40
|
+
foreach(BUILD ${SHADER_BUILDS})
|
|
41
|
+
string(REPLACE "|" ";" PARTS "${BUILD}")
|
|
42
|
+
list(GET PARTS 0 SHADER)
|
|
43
|
+
list(GET PARTS 1 SHADER_NAME)
|
|
44
|
+
list(GET PARTS 2 SHADER_DEFINES)
|
|
45
|
+
|
|
36
46
|
set(SPV_FILE "${SPIRV_OUTPUT_DIR}/${SHADER_NAME}.spv")
|
|
37
47
|
set(SPV_HEADER "${SPIRV_OUTPUT_DIR}/${SHADER_NAME}.spv.h")
|
|
38
48
|
string(REGEX REPLACE "[^a-zA-Z0-9]" "_" ARRAY_NAME "spv_${SHADER_NAME}")
|
|
39
49
|
|
|
50
|
+
set(GLSLC_ARGS -O)
|
|
51
|
+
if (SHADER_DEFINES)
|
|
52
|
+
set(GLSLC_ARGS ${GLSLC_ARGS} "-D${SHADER_DEFINES}")
|
|
53
|
+
endif()
|
|
54
|
+
|
|
40
55
|
add_custom_command(
|
|
41
56
|
OUTPUT "${SPV_FILE}"
|
|
42
|
-
COMMAND "${GLSLC}"
|
|
57
|
+
COMMAND "${GLSLC}" ${GLSLC_ARGS} -o "${SPV_FILE}" "${SHADER}"
|
|
43
58
|
DEPENDS "${SHADER}"
|
|
44
|
-
COMMENT "Compiling ${SHADER_NAME} -> SPIR-V"
|
|
59
|
+
COMMENT "Compiling ${SHADER_NAME} -> SPIR-V (defines: ${SHADER_DEFINES})"
|
|
45
60
|
)
|
|
46
61
|
|
|
47
62
|
# Generate a C header embedding the SPIR-V as a uint32_t array
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
// Pre-compiled SPIR-V headers (generated at build time by glslc + spv_to_header.cmake)
|
|
51
51
|
#include "terrain.vert.spv.h"
|
|
52
52
|
#include "terrain.frag.spv.h"
|
|
53
|
+
#include "terrain_dither.frag.spv.h"
|
|
53
54
|
#include "sky.vert.spv.h"
|
|
54
55
|
#include "sky.frag.spv.h"
|
|
55
56
|
|
|
@@ -109,7 +110,7 @@ struct TerrainFragmentPC {
|
|
|
109
110
|
float translationY; // 4 bytes
|
|
110
111
|
float scaleX; // 4 bytes
|
|
111
112
|
float scaleY; // 4 bytes
|
|
112
|
-
float
|
|
113
|
+
float lodFade; // 4 bytes — LOD dither threshold
|
|
113
114
|
}; // 64 bytes
|
|
114
115
|
|
|
115
116
|
namespace reactnativecesium {
|
|
@@ -296,6 +297,10 @@ void VulkanBackend::shutdown() {
|
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
for (int i = 0; i < kMaxFramesInFlight; ++i) {
|
|
300
|
+
if (vtxMapped_[i]) { vkUnmapMemory(device_, vtxMems_[i]); vtxMapped_[i] = nullptr; }
|
|
301
|
+
if (idxMapped_[i]) { vkUnmapMemory(device_, idxMems_[i]); idxMapped_[i] = nullptr; }
|
|
302
|
+
if (uvMapped_[i]) { vkUnmapMemory(device_, uvMems_[i]); uvMapped_[i] = nullptr; }
|
|
303
|
+
if (altMapped_[i]) { vkUnmapMemory(device_, altMems_[i]); altMapped_[i] = nullptr; }
|
|
299
304
|
if (vtxBufs_[i]) vkDestroyBuffer(device_, vtxBufs_[i], nullptr);
|
|
300
305
|
if (vtxMems_[i]) vkFreeMemory(device_, vtxMems_[i], nullptr);
|
|
301
306
|
if (idxBufs_[i]) vkDestroyBuffer(device_, idxBufs_[i], nullptr);
|
|
@@ -858,7 +863,7 @@ void VulkanBackend::createSyncObjects() {
|
|
|
858
863
|
// renderFinishedSemaphores_ created in beginFrame after swapchain is ready
|
|
859
864
|
}
|
|
860
865
|
|
|
861
|
-
// ── Pipeline cache (
|
|
866
|
+
// ── Pipeline cache (vk-pipeline-cache) ───────────────────────────────────────
|
|
862
867
|
|
|
863
868
|
namespace {
|
|
864
869
|
std::string pipelineCachePath(const std::string& cacheDir) {
|
|
@@ -1173,9 +1178,6 @@ void VulkanBackend::createSkyPipeline() {
|
|
|
1173
1178
|
}
|
|
1174
1179
|
|
|
1175
1180
|
void VulkanBackend::createTerrainPipeline() {
|
|
1176
|
-
VkShaderModule vertModule = createShaderModule(device_, spv_terrain_vert, spv_terrain_vert_size);
|
|
1177
|
-
VkShaderModule fragModule = createShaderModule(device_, spv_terrain_frag, spv_terrain_frag_size);
|
|
1178
|
-
|
|
1179
1181
|
// Vertex bindings: position (vec3) | uv (vec2) | altitude (float).
|
|
1180
1182
|
static const VkVertexInputBindingDescription bindings[3] = {
|
|
1181
1183
|
{0, 3 * sizeof(float), VK_VERTEX_INPUT_RATE_VERTEX},
|
|
@@ -1188,22 +1190,41 @@ void VulkanBackend::createTerrainPipeline() {
|
|
|
1188
1190
|
{2, 2, VK_FORMAT_R32_SFLOAT, 0},
|
|
1189
1191
|
};
|
|
1190
1192
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1193
|
+
VkShaderModule vertModule = createShaderModule(
|
|
1194
|
+
device_, spv_terrain_vert, spv_terrain_vert_size);
|
|
1195
|
+
VkShaderModule fragSolidModule = createShaderModule(
|
|
1196
|
+
device_, spv_terrain_frag, spv_terrain_frag_size);
|
|
1197
|
+
VkShaderModule fragDitherModule = createShaderModule(
|
|
1198
|
+
device_, spv_terrain_dither_frag, spv_terrain_dither_frag_size);
|
|
1199
|
+
|
|
1200
|
+
auto buildOne = [&](VkShaderModule fragModule) {
|
|
1201
|
+
PipelineBuildSpec spec{};
|
|
1202
|
+
spec.vert = vertModule;
|
|
1203
|
+
spec.frag = fragModule;
|
|
1204
|
+
spec.bindingCount = 3;
|
|
1205
|
+
spec.bindings = bindings;
|
|
1206
|
+
spec.attributeCount = 3;
|
|
1207
|
+
spec.attributes = attrs;
|
|
1208
|
+
spec.depthTest = true;
|
|
1209
|
+
spec.depthWrite = true;
|
|
1210
|
+
spec.cull = VK_CULL_MODE_BACK_BIT;
|
|
1211
|
+
spec.samples = sampleCount_;
|
|
1212
|
+
return buildPipeline(device_, terrainPipelineLayout_, renderPass_,
|
|
1213
|
+
pipelineCache_, spec);
|
|
1214
|
+
};
|
|
1204
1215
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1216
|
+
// Solid pipeline: no LOD-fade discard in the fragment shader, so the GPU's
|
|
1217
|
+
// early-Z optimisation runs at full speed. This is the pipeline used for
|
|
1218
|
+
// the overwhelming majority of draws outside an active LOD transition.
|
|
1219
|
+
terrainSolidPipeline_ = buildOne(fragSolidModule);
|
|
1220
|
+
// Dither pipeline: includes IGN-based discard. Used only for draws where
|
|
1221
|
+
// DrawPrimitive::lodFade < 1.0f (see CesiumEngine::updateFrame which
|
|
1222
|
+
// partitions draws so the switch happens at most once per frame).
|
|
1223
|
+
terrainDitherPipeline_ = buildOne(fragDitherModule);
|
|
1224
|
+
|
|
1225
|
+
vkDestroyShaderModule(device_, vertModule, nullptr);
|
|
1226
|
+
vkDestroyShaderModule(device_, fragSolidModule, nullptr);
|
|
1227
|
+
vkDestroyShaderModule(device_, fragDitherModule, nullptr);
|
|
1207
1228
|
}
|
|
1208
1229
|
|
|
1209
1230
|
void VulkanBackend::createGraphicsPipelinesAll() {
|
|
@@ -1212,9 +1233,12 @@ void VulkanBackend::createGraphicsPipelinesAll() {
|
|
|
1212
1233
|
}
|
|
1213
1234
|
|
|
1214
1235
|
void VulkanBackend::destroyGraphicsPipelinesAll() {
|
|
1215
|
-
if (
|
|
1216
|
-
if (
|
|
1217
|
-
|
|
1236
|
+
if (terrainSolidPipeline_) vkDestroyPipeline(device_, terrainSolidPipeline_, nullptr);
|
|
1237
|
+
if (terrainDitherPipeline_) vkDestroyPipeline(device_, terrainDitherPipeline_, nullptr);
|
|
1238
|
+
if (skyPipeline_) vkDestroyPipeline(device_, skyPipeline_, nullptr);
|
|
1239
|
+
terrainSolidPipeline_ = VK_NULL_HANDLE;
|
|
1240
|
+
terrainDitherPipeline_ = VK_NULL_HANDLE;
|
|
1241
|
+
skyPipeline_ = VK_NULL_HANDLE;
|
|
1218
1242
|
}
|
|
1219
1243
|
|
|
1220
1244
|
// ── Memory / buffer helpers ─────────────────────────────────────────────────────
|
|
@@ -1285,23 +1309,29 @@ bool VulkanBackend::createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
|
|
|
1285
1309
|
}
|
|
1286
1310
|
|
|
1287
1311
|
void VulkanBackend::ensureBuffer(VkBuffer& buffer, VkDeviceMemory& memory,
|
|
1288
|
-
size_t& capacity, size_t needed,
|
|
1312
|
+
void*& mapped, size_t& capacity, size_t needed,
|
|
1289
1313
|
const void* data, VkBufferUsageFlags usage) {
|
|
1290
1314
|
if (needed == 0) return;
|
|
1291
1315
|
if (needed > capacity) {
|
|
1316
|
+
if (mapped) { vkUnmapMemory(device_, memory); mapped = nullptr; }
|
|
1292
1317
|
if (buffer) vkDestroyBuffer(device_, buffer, nullptr);
|
|
1293
1318
|
if (memory) vkFreeMemory(device_, memory, nullptr);
|
|
1294
1319
|
size_t newCap = std::max(needed, capacity > 0 ? capacity * 2 : needed);
|
|
1295
1320
|
newCap = (newCap + 4095UL) & ~4095UL;
|
|
1296
|
-
createBuffer(static_cast<VkDeviceSize>(newCap), usage,
|
|
1297
|
-
|
|
1298
|
-
|
|
1321
|
+
if (!createBuffer(static_cast<VkDeviceSize>(newCap), usage,
|
|
1322
|
+
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
|
|
1323
|
+
buffer, memory)) {
|
|
1324
|
+
capacity = 0;
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1299
1327
|
capacity = newCap;
|
|
1328
|
+
if (vkMapMemory(device_, memory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS) {
|
|
1329
|
+
mapped = nullptr;
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1300
1332
|
}
|
|
1301
|
-
|
|
1302
|
-
if (vkMapMemory(device_, memory, 0, needed, 0, &mapped) != VK_SUCCESS) return;
|
|
1333
|
+
if (!mapped) return;
|
|
1303
1334
|
memcpy(mapped, data, needed);
|
|
1304
|
-
vkUnmapMemory(device_, memory);
|
|
1305
1335
|
}
|
|
1306
1336
|
|
|
1307
1337
|
VkCommandBuffer VulkanBackend::beginSingleTimeCommands() {
|
|
@@ -1449,25 +1479,34 @@ void VulkanBackend::initStagingRing(VkDeviceSize bytes) {
|
|
|
1449
1479
|
stagingMemory_ = VK_NULL_HANDLE;
|
|
1450
1480
|
return;
|
|
1451
1481
|
}
|
|
1452
|
-
stagingMapped_
|
|
1453
|
-
stagingHead_
|
|
1482
|
+
stagingMapped_ = static_cast<uint8_t*>(mapped);
|
|
1483
|
+
stagingHead_ = 0;
|
|
1484
|
+
stagingBytesThisBatch_ = 0;
|
|
1454
1485
|
}
|
|
1455
1486
|
|
|
1456
1487
|
VkDeviceSize VulkanBackend::allocateStagingSlice(VkDeviceSize size) {
|
|
1457
1488
|
if (!stagingMapped_ || size == 0 || size > kStagingSize) return SIZE_MAX;
|
|
1458
1489
|
|
|
1459
1490
|
VkDeviceSize aligned = (size + kStagingAlignment - 1) & ~(kStagingAlignment - 1);
|
|
1491
|
+
|
|
1492
|
+
// per-batch budget. Refuse the allocation if granting it would let a
|
|
1493
|
+
// single beginFrame->flushPendingUploads cycle eat more than 1/N of the
|
|
1494
|
+
// ring. The caller (createImageFromPixels) falls back to the synchronous
|
|
1495
|
+
// single-tile path for that tile so the rest of the batch still goes via
|
|
1496
|
+
// the async ring.
|
|
1497
|
+
if (stagingBytesThisBatch_ + aligned > kStagingPerFrameBudget) {
|
|
1498
|
+
return SIZE_MAX;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1460
1501
|
VkDeviceSize off;
|
|
1461
1502
|
if (stagingHead_ + aligned > kStagingSize) {
|
|
1462
|
-
// Wrap to the start of the ring. Anything still in flight from before the
|
|
1463
|
-
// wrap will keep its previous offset alive until its upload fence
|
|
1464
|
-
// signalled in beginFrame.
|
|
1465
1503
|
off = 0;
|
|
1466
1504
|
stagingHead_ = aligned;
|
|
1467
1505
|
} else {
|
|
1468
1506
|
off = stagingHead_;
|
|
1469
1507
|
stagingHead_ += aligned;
|
|
1470
1508
|
}
|
|
1509
|
+
stagingBytesThisBatch_ += aligned;
|
|
1471
1510
|
return off;
|
|
1472
1511
|
}
|
|
1473
1512
|
|
|
@@ -1486,7 +1525,7 @@ void VulkanBackend::flushPendingUploads() {
|
|
|
1486
1525
|
LOGE("flushPendingUploads: upload command buffer is NULL!");
|
|
1487
1526
|
return;
|
|
1488
1527
|
}
|
|
1489
|
-
|
|
1528
|
+
|
|
1490
1529
|
vkResetCommandBuffer(cmd, 0);
|
|
1491
1530
|
|
|
1492
1531
|
VkCommandBufferBeginInfo bi{};
|
|
@@ -1506,7 +1545,7 @@ void VulkanBackend::flushPendingUploads() {
|
|
|
1506
1545
|
safeMipLevels = 1;
|
|
1507
1546
|
u.tex->mipLevels = 1;
|
|
1508
1547
|
}
|
|
1509
|
-
|
|
1548
|
+
|
|
1510
1549
|
VkImageMemoryBarrier b{};
|
|
1511
1550
|
b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
1512
1551
|
b.srcAccessMask = 0;
|
|
@@ -1574,23 +1613,41 @@ void VulkanBackend::flushPendingUploads() {
|
|
|
1574
1613
|
|
|
1575
1614
|
vkEndCommandBuffer(cmd);
|
|
1576
1615
|
|
|
1577
|
-
//
|
|
1578
|
-
//
|
|
1616
|
+
// async submit. The fence lets beginFrame reclaim the staging slices in
|
|
1617
|
+
// kMaxFramesInFlight frames' time without ever calling vkQueueWaitIdle.
|
|
1618
|
+
// Ordering: this submit precedes endFrame()'s draw submit on the same
|
|
1619
|
+
// graphicsQueue_, so the image layout transitions recorded above are
|
|
1620
|
+
// visible to subsequent fragment-shader reads via the standard in-queue
|
|
1621
|
+
// execution + memory-barrier rules — no extra semaphore needed.
|
|
1622
|
+
vkResetFences(device_, 1, &uploadFences_[frameIndex_]);
|
|
1623
|
+
|
|
1579
1624
|
VkSubmitInfo si{};
|
|
1580
1625
|
si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
1581
1626
|
si.commandBufferCount = 1;
|
|
1582
1627
|
si.pCommandBuffers = &cmd;
|
|
1583
|
-
|
|
1584
|
-
VkResult submitResult =
|
|
1628
|
+
|
|
1629
|
+
VkResult submitResult =
|
|
1630
|
+
vkQueueSubmit(graphicsQueue_, 1, &si, uploadFences_[frameIndex_]);
|
|
1585
1631
|
if (submitResult != VK_SUCCESS) {
|
|
1586
1632
|
LOGE("flushPendingUploads: vkQueueSubmit failed: %d", submitResult);
|
|
1587
1633
|
return;
|
|
1588
1634
|
}
|
|
1589
|
-
|
|
1590
|
-
vkQueueWaitIdle(graphicsQueue_);
|
|
1591
1635
|
|
|
1592
1636
|
// Mark each texture as visible to subsequent draws (no longer pending).
|
|
1637
|
+
// Safe to flip here because the next draw submit happens after this one on
|
|
1638
|
+
// the same queue, and the upload CB ends in a TRANSFER → FRAGMENT_SHADER
|
|
1639
|
+
// memory barrier.
|
|
1593
1640
|
for (auto& u : uploads) u.tex->pendingUpload = false;
|
|
1641
|
+
|
|
1642
|
+
// Move uploads into this slot's in-flight list so beginFrame can free
|
|
1643
|
+
// their staging slices once uploadFences_[frameIndex_] is signalled (which
|
|
1644
|
+
// happens at most kMaxFramesInFlight frames from now).
|
|
1645
|
+
{
|
|
1646
|
+
std::lock_guard<std::mutex> lk(stagingMutex_);
|
|
1647
|
+
auto& inflight = uploadsInFlight_[frameIndex_];
|
|
1648
|
+
inflight.insert(inflight.end(), uploads.begin(), uploads.end());
|
|
1649
|
+
stagingBytesThisBatch_ = 0;
|
|
1650
|
+
}
|
|
1594
1651
|
}
|
|
1595
1652
|
|
|
1596
1653
|
// ── Generic image creator (P2-pipelines-dedupe) ─────────────────────────────
|
|
@@ -1898,13 +1955,13 @@ void VulkanBackend::recreateSwapchain() {
|
|
|
1898
1955
|
|
|
1899
1956
|
void VulkanBackend::beginFrame(const FrameParams& /*params*/) {
|
|
1900
1957
|
if (device_ == VK_NULL_HANDLE) return;
|
|
1901
|
-
|
|
1958
|
+
|
|
1902
1959
|
// On Android, defer swapchain creation until the first frame to ensure the
|
|
1903
1960
|
// surface compositor is fully ready. Creating it too early can cause
|
|
1904
1961
|
// vkAcquireNextImageKHR to block indefinitely.
|
|
1905
1962
|
if (swapchain_ == VK_NULL_HANDLE) {
|
|
1906
1963
|
createSwapchain();
|
|
1907
|
-
|
|
1964
|
+
|
|
1908
1965
|
// Create renderFinishedSemaphores now that we know swapchainImages_.size()
|
|
1909
1966
|
renderFinishedSemaphores_.resize(swapchainImages_.size());
|
|
1910
1967
|
VkSemaphoreCreateInfo semInfo{};
|
|
@@ -1912,7 +1969,7 @@ void VulkanBackend::beginFrame(const FrameParams& /*params*/) {
|
|
|
1912
1969
|
for (size_t i = 0; i < renderFinishedSemaphores_.size(); ++i) {
|
|
1913
1970
|
VK_CHECK(vkCreateSemaphore(device_, &semInfo, nullptr, &renderFinishedSemaphores_[i]));
|
|
1914
1971
|
}
|
|
1915
|
-
|
|
1972
|
+
|
|
1916
1973
|
createRenderPass();
|
|
1917
1974
|
createDepthResources();
|
|
1918
1975
|
if (sampleCount_ != VK_SAMPLE_COUNT_1_BIT) createMsaaResources();
|
|
@@ -1958,11 +2015,14 @@ void VulkanBackend::beginFrame(const FrameParams& /*params*/) {
|
|
|
1958
2015
|
++totalFrameCount_;
|
|
1959
2016
|
flushPendingDeletes();
|
|
1960
2017
|
|
|
1961
|
-
//
|
|
1962
|
-
//
|
|
1963
|
-
//
|
|
1964
|
-
//
|
|
1965
|
-
//
|
|
2018
|
+
// reclaim staging slices owned by this slot's previous async upload
|
|
2019
|
+
// submit. The upload fence is created SIGNALED, so the first wait through
|
|
2020
|
+
// each slot is a no-op; on subsequent passes it ensures the GPU has finished
|
|
2021
|
+
// reading those staging bytes before we let allocateStagingSlice hand them
|
|
2022
|
+
// back out.
|
|
2023
|
+
if (uploadFences_[frameIndex_] != VK_NULL_HANDLE) {
|
|
2024
|
+
vkWaitForFences(device_, 1, &uploadFences_[frameIndex_], VK_TRUE, UINT64_MAX);
|
|
2025
|
+
}
|
|
1966
2026
|
{
|
|
1967
2027
|
std::lock_guard<std::mutex> lk(stagingMutex_);
|
|
1968
2028
|
uploadsInFlight_[frameIndex_].clear();
|
|
@@ -1976,7 +2036,7 @@ void VulkanBackend::beginFrame(const FrameParams& /*params*/) {
|
|
|
1976
2036
|
VkResult result = vkAcquireNextImageKHR(device_, swapchain_, 1000000000ULL,
|
|
1977
2037
|
imageAvailableSemaphores_[frameIndex_],
|
|
1978
2038
|
VK_NULL_HANDLE, &imageIndex_);
|
|
1979
|
-
|
|
2039
|
+
|
|
1980
2040
|
if (result == VK_TIMEOUT) {
|
|
1981
2041
|
LOGE("VulkanBackend::beginFrame: vkAcquireNextImageKHR timed out");
|
|
1982
2042
|
frameBegan_ = false;
|
|
@@ -2065,26 +2125,48 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
|
|
|
2065
2125
|
vkCmdDraw(cmd, 3, 1, 0, 0);
|
|
2066
2126
|
}
|
|
2067
2127
|
|
|
2068
|
-
if (frame.draws.empty() ||
|
|
2069
|
-
frame.altitudes.empty() || !terrainPipeline_) {
|
|
2128
|
+
if (frame.draws.empty() || !terrainSolidPipeline_ || !terrainDitherPipeline_) {
|
|
2070
2129
|
return;
|
|
2071
2130
|
}
|
|
2072
2131
|
|
|
2073
2132
|
const int fi = frameIndex_;
|
|
2074
2133
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
const
|
|
2134
|
+
// check the per-slot geometry signature cache. When it matches we
|
|
2135
|
+
// know the persistent vtx/idx/uv/alt buffers already hold the right bytes
|
|
2136
|
+
// and the engine intentionally left the merged CPU arrays empty.
|
|
2137
|
+
const uint64_t sig = frame.geometrySignature;
|
|
2138
|
+
const bool slotMatches = (sig != 0ULL) && (sig == geomSlotSig_[fi]);
|
|
2079
2139
|
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2140
|
+
if (!slotMatches && (frame.localPositions.empty() || frame.indices.empty() ||
|
|
2141
|
+
frame.altitudes.empty())) {
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
if (!slotMatches) {
|
|
2146
|
+
const size_t vtxBytes = frame.localPositions.size() * sizeof(float);
|
|
2147
|
+
const size_t idxBytes = frame.indices.size() * sizeof(uint32_t);
|
|
2148
|
+
const size_t uvBytes = frame.uvs.size() * sizeof(float);
|
|
2149
|
+
const size_t altBytes = frame.altitudes.size() * sizeof(float);
|
|
2150
|
+
|
|
2151
|
+
ensureBuffer(vtxBufs_[fi], vtxMems_[fi], vtxMapped_[fi], vtxCaps_[fi],
|
|
2152
|
+
vtxBytes, frame.localPositions.data(),
|
|
2153
|
+
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
|
2154
|
+
ensureBuffer(idxBufs_[fi], idxMems_[fi], idxMapped_[fi], idxCaps_[fi],
|
|
2155
|
+
idxBytes, frame.indices.data(),
|
|
2156
|
+
VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
|
|
2157
|
+
ensureBuffer(uvBufs_[fi], uvMems_[fi], uvMapped_[fi], uvCaps_[fi],
|
|
2158
|
+
uvBytes, frame.uvs.data(),
|
|
2159
|
+
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
|
2160
|
+
ensureBuffer(altBufs_[fi], altMems_[fi], altMapped_[fi], altCaps_[fi],
|
|
2161
|
+
altBytes, frame.altitudes.data(),
|
|
2162
|
+
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
|
2163
|
+
|
|
2164
|
+
// Record what this slot now holds — set only after every buffer has
|
|
2165
|
+
// been refreshed. If `sig == 0` we deliberately propagate it so future
|
|
2166
|
+
// frames re-upload on this slot (e.g. an active fade is invalidating
|
|
2167
|
+
// the cache).
|
|
2168
|
+
geomSlotSig_[fi] = sig;
|
|
2169
|
+
}
|
|
2088
2170
|
|
|
2089
2171
|
if (!vtxBufs_[fi] || !idxBufs_[fi]) return;
|
|
2090
2172
|
|
|
@@ -2095,7 +2177,11 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
|
|
|
2095
2177
|
terrU.cameraEcef[3] = 0.0f;
|
|
2096
2178
|
memcpy(terrainUboMapped_[fi], &terrU, sizeof(TerrainUBO));
|
|
2097
2179
|
|
|
2098
|
-
|
|
2180
|
+
// Start with the solid pipeline. CesiumEngine has stable_partition-ed draws so
|
|
2181
|
+
// every lodFade==1 draw is first; we only switch to the dither pipeline once,
|
|
2182
|
+
// when the first fading draw is reached.
|
|
2183
|
+
VkPipeline currentPipeline = terrainSolidPipeline_;
|
|
2184
|
+
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, currentPipeline);
|
|
2099
2185
|
|
|
2100
2186
|
VkBuffer vertexBuffers[] = {vtxBufs_[fi], uvBufs_[fi], altBufs_[fi]};
|
|
2101
2187
|
VkDeviceSize offsets[] = {0, 0, 0};
|
|
@@ -2125,6 +2211,18 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
|
|
|
2125
2211
|
for (const auto& draw : frame.draws) {
|
|
2126
2212
|
if (draw.indexCount == 0) continue;
|
|
2127
2213
|
|
|
2214
|
+
// ── Pipeline switch for LOD-fade dither ─────────────────────────────────
|
|
2215
|
+
// Solid pipeline (no discard) for fully-visible tiles keeps Adreno/Mali
|
|
2216
|
+
// early-Z enabled at full speed; dither pipeline only used for actively-
|
|
2217
|
+
// fading tiles. Draws are partitioned so this switch fires at most once
|
|
2218
|
+
// per frame.
|
|
2219
|
+
VkPipeline wantedPipeline = (draw.lodFade < 1.0f)
|
|
2220
|
+
? terrainDitherPipeline_ : terrainSolidPipeline_;
|
|
2221
|
+
if (wantedPipeline != currentPipeline) {
|
|
2222
|
+
currentPipeline = wantedPipeline;
|
|
2223
|
+
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, currentPipeline);
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2128
2226
|
const float* mp = glm::value_ptr(draw.mvpMatrix);
|
|
2129
2227
|
for (int i = 0; i < 16; ++i) pc.v.mvpMatrix[i] = mp[i];
|
|
2130
2228
|
|
|
@@ -2143,6 +2241,7 @@ void VulkanBackend::drawScene(const FrameResult& frame) {
|
|
|
2143
2241
|
pc.f.translationY = draw.overlayTranslation.y;
|
|
2144
2242
|
pc.f.scaleX = draw.overlayScale.x;
|
|
2145
2243
|
pc.f.scaleY = draw.overlayScale.y;
|
|
2244
|
+
pc.f.lodFade = draw.lodFade;
|
|
2146
2245
|
vkCmdPushConstants(cmd, terrainPipelineLayout_,
|
|
2147
2246
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
2148
2247
|
0, sizeof(CombinedPC), &pc);
|
|
@@ -104,8 +104,14 @@ private:
|
|
|
104
104
|
bool createBuffer(VkDeviceSize size, VkBufferUsageFlags usage,
|
|
105
105
|
VkMemoryPropertyFlags props, VkBuffer& buffer,
|
|
106
106
|
VkDeviceMemory& memory);
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// Grow `buffer`/`memory` if needed and copy `data` (size = needed bytes)
|
|
108
|
+
// into it via the persistently mapped pointer (`mapped`). On reallocation
|
|
109
|
+
// the previous mapping is unmapped and `mapped` is rewritten with the new
|
|
110
|
+
// map; otherwise the existing map is reused, saving a map/unmap pair per
|
|
111
|
+
// frame for each of the four merged terrain buffers.
|
|
112
|
+
void ensureBuffer(VkBuffer& buffer, VkDeviceMemory& memory, void*& mapped,
|
|
113
|
+
size_t& capacity, size_t needed, const void* data,
|
|
114
|
+
VkBufferUsageFlags usage);
|
|
109
115
|
|
|
110
116
|
// ── Deferred upload pipeline ─────────────────────────────────────────────
|
|
111
117
|
// Each call to createRasterTexture / createWaterMaskTexture allocates a slice
|
|
@@ -198,7 +204,12 @@ private:
|
|
|
198
204
|
VkDescriptorSetLayout skyDescSetLayout_ = VK_NULL_HANDLE;
|
|
199
205
|
VkPipelineLayout terrainPipelineLayout_ = VK_NULL_HANDLE;
|
|
200
206
|
VkPipelineLayout skyPipelineLayout_ = VK_NULL_HANDLE;
|
|
201
|
-
|
|
207
|
+
// Two terrain pipelines built from the same GLSL source (compiled twice via
|
|
208
|
+
// -DTERRAIN_DITHER=1 in android/CMakeLists.txt). The solid pipeline omits
|
|
209
|
+
// the LOD-fade discard so early-Z stays enabled at full speed on Adreno/Mali;
|
|
210
|
+
// the dither pipeline is used only for draws where DrawPrimitive::lodFade<1.
|
|
211
|
+
VkPipeline terrainSolidPipeline_ = VK_NULL_HANDLE;
|
|
212
|
+
VkPipeline terrainDitherPipeline_ = VK_NULL_HANDLE;
|
|
202
213
|
VkPipeline skyPipeline_ = VK_NULL_HANDLE;
|
|
203
214
|
|
|
204
215
|
VkDescriptorPool descriptorPool_ = VK_NULL_HANDLE;
|
|
@@ -234,22 +245,38 @@ private:
|
|
|
234
245
|
uint32_t imageIndex_ = 0;
|
|
235
246
|
bool frameBegan_ = false;
|
|
236
247
|
|
|
237
|
-
// Triple-buffered persistent vertex/index/UV/altitude buffers
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
VkBuffer
|
|
243
|
-
VkDeviceMemory
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
248
|
+
// Triple-buffered persistent vertex/index/UV/altitude buffers.
|
|
249
|
+
// The mapped pointers are kept alive for the lifetime of the buffer (only
|
|
250
|
+
// unmapped when the underlying memory is freed on reallocation/shutdown),
|
|
251
|
+
// mirroring how skyUboMapped_ / terrainUboMapped_ are handled — this saves
|
|
252
|
+
// four map/unmap pairs per frame.
|
|
253
|
+
VkBuffer vtxBufs_[kMaxFramesInFlight] = {};
|
|
254
|
+
VkDeviceMemory vtxMems_[kMaxFramesInFlight] = {};
|
|
255
|
+
void* vtxMapped_[kMaxFramesInFlight] = {};
|
|
256
|
+
size_t vtxCaps_[kMaxFramesInFlight] = {};
|
|
257
|
+
|
|
258
|
+
VkBuffer idxBufs_[kMaxFramesInFlight] = {};
|
|
259
|
+
VkDeviceMemory idxMems_[kMaxFramesInFlight] = {};
|
|
260
|
+
void* idxMapped_[kMaxFramesInFlight] = {};
|
|
261
|
+
size_t idxCaps_[kMaxFramesInFlight] = {};
|
|
262
|
+
|
|
263
|
+
VkBuffer uvBufs_[kMaxFramesInFlight] = {};
|
|
264
|
+
VkDeviceMemory uvMems_[kMaxFramesInFlight] = {};
|
|
265
|
+
void* uvMapped_[kMaxFramesInFlight] = {};
|
|
266
|
+
size_t uvCaps_[kMaxFramesInFlight] = {};
|
|
267
|
+
|
|
268
|
+
VkBuffer altBufs_[kMaxFramesInFlight] = {}; // float altitude per vertex
|
|
269
|
+
VkDeviceMemory altMems_[kMaxFramesInFlight] = {};
|
|
270
|
+
void* altMapped_[kMaxFramesInFlight] = {};
|
|
271
|
+
size_t altCaps_[kMaxFramesInFlight] = {};
|
|
272
|
+
|
|
273
|
+
// ── per-slot geometry signature cache ────────────────────────────────
|
|
274
|
+
// When FrameResult::geometrySignature matches the value last written into
|
|
275
|
+
// this slot, the four merged buffers above already contain exactly the
|
|
276
|
+
// bytes the engine would have produced this frame and the host memcpys can
|
|
277
|
+
// be skipped. 0 means "this slot is stale" — backends should always
|
|
278
|
+
// memcpy when geomSlotSig_[fi] is 0 or differs from the incoming sig.
|
|
279
|
+
uint64_t geomSlotSig_[kMaxFramesInFlight] = {};
|
|
253
280
|
|
|
254
281
|
// Per-frame UBO: just cameraEcef (fragment stage, used for lighting vd).
|
|
255
282
|
// MVP is now per-draw via push constants.
|
|
@@ -270,10 +297,24 @@ private:
|
|
|
270
297
|
// beginFrame when the matching upload fence is signalled).
|
|
271
298
|
static constexpr VkDeviceSize kStagingSize = 64ULL * 1024ULL * 1024ULL;
|
|
272
299
|
static constexpr VkDeviceSize kStagingAlignment = 256;
|
|
300
|
+
// Per-batch (per beginFrame flush) allocation budget. With async upload
|
|
301
|
+
// submission, the ring head must never wrap past slices still being read by
|
|
302
|
+
// the GPU. Capping a single batch to kStagingSize / kMaxFramesInFlight
|
|
303
|
+
// guarantees that by the time the head wraps back, the slot that wrote
|
|
304
|
+
// those bytes has already had its uploadFences_ signalled (we wait on it
|
|
305
|
+
// in beginFrame before clearing uploadsInFlight_). When a single batch
|
|
306
|
+
// would exceed the budget the call site falls back to the synchronous
|
|
307
|
+
// per-tile upload path that already exists for the pre-init case.
|
|
308
|
+
static constexpr VkDeviceSize kStagingPerFrameBudget =
|
|
309
|
+
kStagingSize / kMaxFramesInFlight;
|
|
273
310
|
VkBuffer stagingBuffer_ = VK_NULL_HANDLE;
|
|
274
311
|
VkDeviceMemory stagingMemory_ = VK_NULL_HANDLE;
|
|
275
312
|
uint8_t* stagingMapped_ = nullptr;
|
|
276
313
|
VkDeviceSize stagingHead_ = 0;
|
|
314
|
+
// Bytes allocated from the staging ring since the last flush. Reset to 0
|
|
315
|
+
// inside flushPendingUploads() once those bytes have been moved into
|
|
316
|
+
// uploadsInFlight_[frameIndex_].
|
|
317
|
+
VkDeviceSize stagingBytesThisBatch_ = 0;
|
|
277
318
|
VkFence uploadFences_[kMaxFramesInFlight] = {};
|
|
278
319
|
std::vector<PendingUpload> pendingUploadsCurrent_; // queued, not yet submitted
|
|
279
320
|
std::vector<PendingUpload> uploadsInFlight_[kMaxFramesInFlight]; // submitted
|