From 6a74ab15c39406bcf0a0ae304104ddee47b8c2ac Mon Sep 17 00:00:00 2001 From: Fletterio Date: Wed, 23 Jul 2025 20:38:14 -0300 Subject: [PATCH 01/11] Checkpoint 1 is close --- 62_CAD/DrawResourcesFiller.cpp | 146 +++++++++++++++--- 62_CAD/DrawResourcesFiller.h | 27 +++- 62_CAD/Images.h | 6 +- 62_CAD/main.cpp | 26 +++- 62_CAD/scripts/tiled_grid.py | 266 +++++++++++++++++++++++++++++++++ 5 files changed, 443 insertions(+), 28 deletions(-) create mode 100644 62_CAD/scripts/tiled_grid.py diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index b40f6585c..5c2242547 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -631,7 +631,7 @@ bool DrawResourcesFiller::ensureMultipleStaticImagesAvailability(std::spangetLogicalDevice(); auto* physDev = m_utilities->getLogicalDevice()->getPhysicalDevice(); @@ -639,12 +639,11 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( // Try inserting or updating the image usage in the cache. // If the image is already present, updates its semaphore value. auto evictCallback = [&](image_id imageID, const CachedImageRecord& evicted) { evictImage_SubmitIfNeeded(imageID, evicted, intendedNextSubmit); }; - CachedImageRecord* cachedImageRecord = imagesCache->insert(imageID, intendedNextSubmit.getFutureScratchSemaphore().value, evictCallback); + CachedImageRecord* cachedImageRecord = imagesCache->insert(manager.georeferencedImageParams.imageID, intendedNextSubmit.getFutureScratchSemaphore().value, evictCallback); // TODO: Function call that gets you image creaation params based on georeferencedImageParams (extents and mips and whatever), it will also get you the GEOREFERENED TYPE IGPUImage::SCreationParams imageCreationParams = {}; - ImageType georeferenceImageType; - determineGeoreferencedImageCreationParams(imageCreationParams, georeferenceImageType, params); + determineGeoreferencedImageCreationParams(imageCreationParams, manager); // imageParams = cpuImage->getCreationParameters(); imageCreationParams.usage |= IGPUImage::EUF_TRANSFER_DST_BIT|IGPUImage::EUF_SAMPLED_BIT; @@ -671,11 +670,11 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( const auto cachedImageType = cachedImageRecord->type; // image type and creation params (most importantly extent and format) should match, otherwise we evict, recreate and re-pus const auto currentParams = static_cast(imageCreationParams); - const bool needsRecreation = cachedImageType != georeferenceImageType || cachedParams != currentParams; + const bool needsRecreation = cachedImageType != manager.georeferencedImageParams.imageType || cachedParams != currentParams; if (needsRecreation) { // call the eviction callback so the currently cached imageID gets eventually deallocated from memory arena. - evictCallback(imageID, *cachedImageRecord); + evictCallback(manager.georeferencedImageParams.imageID, *cachedImageRecord); // instead of erasing and inserting the imageID into the cache, we just reset it, so the next block of code goes into array index allocation + creating our new image *cachedImageRecord = CachedImageRecord(currentFrameIndex); @@ -705,17 +704,17 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( if (cachedImageRecord->arrayIndex != video::SubAllocatedDescriptorSet::AddressAllocator::invalid_address) { // Attempt to create a GPU image and image view for this texture. - ImageAllocateResults allocResults = tryCreateAndAllocateImage_SubmitIfNeeded(imageCreationParams, asset::E_FORMAT::EF_COUNT, intendedNextSubmit, std::to_string(imageID)); + ImageAllocateResults allocResults = tryCreateAndAllocateImage_SubmitIfNeeded(imageCreationParams, asset::E_FORMAT::EF_COUNT, intendedNextSubmit, std::to_string(manager.georeferencedImageParams.imageID)); if (allocResults.isValid()) { - cachedImageRecord->type = georeferenceImageType; + cachedImageRecord->type = manager.georeferencedImageParams.imageType; cachedImageRecord->state = ImageState::CREATED_AND_MEMORY_BOUND; cachedImageRecord->lastUsedFrameIndex = currentFrameIndex; // there was an eviction + auto-submit, we need to update AGAIN cachedImageRecord->allocationOffset = allocResults.allocationOffset; cachedImageRecord->allocationSize = allocResults.allocationSize; cachedImageRecord->gpuImageView = allocResults.gpuImageView; - cachedImageRecord->staticCPUImage = nullptr; + cachedImageRecord->staticCPUImage = manager.georeferencedImageParams.geoReferencedImage; } else { @@ -743,7 +742,7 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( } // erase the entry we failed to fill, no need for `evictImage_SubmitIfNeeded`, because it didn't get to be used in any submit to defer it's memory and index deallocation - imagesCache->erase(imageID); + imagesCache->erase(manager.georeferencedImageParams.imageID); } } else @@ -1557,7 +1556,7 @@ bool DrawResourcesFiller::pushStreamedImagesUploads(SIntendedSubmitInfo& intende std::vector afterCopyImageBarriers; afterCopyImageBarriers.reserve(streamedImageCopies.size()); - // Pipeline Barriers before imageCopy + // Pipeline Barriers after imageCopy for (auto& [imageID, imageCopies] : streamedImageCopies) { auto* imageRecord = imagesCache->peek(imageID); @@ -2461,30 +2460,35 @@ DrawResourcesFiller::ImageAllocateResults DrawResourcesFiller::tryCreateAndAlloc return ret; } -void DrawResourcesFiller::determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, ImageType& outImageType, const GeoreferencedImageParams& georeferencedImageParams) +void DrawResourcesFiller::determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, StreamedImageManager& manager) { + auto& georeferencedImageParams = manager.georeferencedImageParams; // Decide whether the image can reside fully into memory rather than get streamed. // TODO: Improve logic, currently just a simple check to see if the full-screen image has more pixels that viewport or not // TODO: add criterial that the size of the full-res image shouldn't consume more than 30% of the total memory arena for images (if we allowed larger than viewport extents) const bool betterToResideFullyInMem = georeferencedImageParams.imageExtents.x * georeferencedImageParams.imageExtents.y <= georeferencedImageParams.viewportExtents.x * georeferencedImageParams.viewportExtents.y; if (betterToResideFullyInMem) - outImageType = ImageType::GEOREFERENCED_FULL_RESOLUTION; + georeferencedImageParams.imageType = ImageType::GEOREFERENCED_FULL_RESOLUTION; else - outImageType = ImageType::GEOREFERENCED_STREAMED; + georeferencedImageParams.imageType = ImageType::GEOREFERENCED_STREAMED; outImageParams.type = asset::IImage::ET_2D; outImageParams.samples = asset::IImage::ESCF_1_BIT; outImageParams.format = georeferencedImageParams.format; - if (outImageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) + if (georeferencedImageParams.imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) { outImageParams.extent = { georeferencedImageParams.imageExtents.x, georeferencedImageParams.imageExtents.y, 1u }; } else { - // TODO: Better Logic, area around the view, etc... - outImageParams.extent = { georeferencedImageParams.viewportExtents.x, georeferencedImageParams.viewportExtents.y, 1u }; + // Pad sides to multiple of tileSize. Even after rounding up, we might still need to add an extra tile to cover both sides. + const auto xExtent = core::roundUp(georeferencedImageParams.viewportExtents.x, manager.TileSize) + manager.TileSize; + const auto yExtent = core::roundUp(georeferencedImageParams.viewportExtents.y, manager.TileSize) + manager.TileSize; + outImageParams.extent = { xExtent, yExtent, 1u }; + manager.maxResidentTiles.x = xExtent / manager.TileSize; + manager.maxResidentTiles.y = yExtent / manager.TileSize; } @@ -2624,4 +2628,112 @@ void DrawResourcesFiller::flushDrawObjects() drawCalls.push_back(drawCall); drawObjectsFlushedToDrawCalls = resourcesCollection.drawObjects.getCount(); } +} + +DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedImageParams&& _georeferencedImageParams) + : georeferencedImageParams(std::move(_georeferencedImageParams)) +{ + maxImageTileIndices = georeferencedImageParams.imageExtents / uint32_t2(TileSize, TileSize); + // If it fits perfectly along any dimension, we need one less tile with this scheme + maxImageTileIndices -= uint32_t2(maxImageTileIndices.x * TileSize == georeferencedImageParams.imageExtents.x, maxImageTileIndices.y * TileSize == georeferencedImageParams.imageExtents.y); + + // R^2 can be covered with a lattice of image tiles. Real tiles (those actually covered by the image) are indexed in the range [0, maxImageTileIndices.x] x [0, maxImageTileIndices.y], + // but part of the algorithm to figure out which tiles need to be resident for a draw involves figuring out the coordinates in this lattice of each of the viewport corners. + // To that end, we devise an algorithm that maps a point in worldspace to its coordinates in this tile lattice: + // 1. Get the displacement (will be an offset vector in world coords and world units) from the `topLeft` corner of the image to the point + // 2. Transform this displacement vector into a displacement into the coordinates spanned by the basis {dirU, dirV}. Notice that these vectors are still in world units + // 3. Map world units to tile units. This scaling is generally nonuniform, since it depends on the ratio of pixels to world units per coordinate. + // The name of the `offsetCoBScaleMatrix` follows by what is computed at each step + + // 1. Displacement. The following matrix calculates the offset for an input point `p` with homogenous worldspace coordinates. + // By foregoing the homogenous coordinate we can keep only the vector part, that's why it's `2x3` and not `3x3` + float64_t2 topLeftWorld = georeferencedImageParams.worldspaceOBB.topLeft; + float64_t2x3 displacementMatrix(1., 0., topLeftWorld.x, 0., 1., topLeftWorld.y); + + // 2. Change of Basis. Since {dirU, dirV} are orthogonal, the matrix to change from world coords to "image worldspan" coords has a quite nice expression + float64_t2 dirU = georeferencedImageParams.worldspaceOBB.dirU; + float64_t2 dirV = float32_t2(dirU.y, -dirU.x) * georeferencedImageParams.worldspaceOBB.aspectRatio; + float64_t dirULengthSquared = nbl::hlsl::dot(dirU, dirU); + float64_t dirVLengthSquared = nbl::hlsl::dot(dirV, dirV); + float64_t2 firstRow = dirU / dirULengthSquared; + float64_t2 secondRow = dirV / dirVLengthSquared; + float64_t2x2 changeOfBasisMatrix(firstRow, secondRow); + + // 3. Scaling. The vector obtained by doing `CoB * displacement * p` is still in world units. Given that we know how many pixels the image spans (given by + // georeferencedImageParams.imageExtents) and how many world units it spans (given by (|dirU|, |dirV|) ) we can get a factor for the `pixel/world unit` ratio. + // Then we simply multiply that factor for another factor for the `tile / pixel` ratio to get our `tile / world unit` scaling factor. + float64_t dirULength = nbl::hlsl::sqrt(dirULengthSquared); + float64_t dirVLength = nbl::hlsl::sqrt(dirVLengthSquared); + float64_t2 scaleFactors = (1. / TileSize) * (float64_t2(georeferencedImageParams.imageExtents) / float64_t2(dirULength, dirVLength)); + float64_t2x2 scaleMatrix(scaleFactors.x, 0., 0., scaleFactors.y); + + // Put them all together + offsetCoBScaleMatrix = nbl::hlsl::mul(scaleMatrix, nbl::hlsl::mul(changeOfBasisMatrix, displacementMatrix)); +} + +core::vector DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const float64_t3x3& NDCToWorld) +{ + // Using Vulkan NDC, the viewport has coordinates in the range [-1, -1] x [1,1]. First we get the world coordinates of the viewport corners, in homogenous + float64_t3 topLeftNDCH(-1., -1., 1.); + float64_t3 topRightNDCH(1., -1., 1.); + float64_t3 bottomLeftNDCH(-1., 1., 1.); + float64_t3 bottomRightNDCH(1., 1., 1.); + + float64_t3 topLeftWorldH = nbl::hlsl::mul(NDCToWorld, topLeftNDCH); + float64_t3 topRightWorldH = nbl::hlsl::mul(NDCToWorld, topRightNDCH); + float64_t3 bottomLeftWorldH = nbl::hlsl::mul(NDCToWorld, bottomLeftNDCH); + float64_t3 bottomRightWorldH = nbl::hlsl::mul(NDCToWorld, bottomRightNDCH); + + // We can use `offsetCoBScaleMatrix` to get tile lattice coordinates for each of these points + float64_t2 topLeftTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, topLeftWorldH); + float64_t2 topRightTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, topRightWorldH); + float64_t2 bottomLeftTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, bottomLeftWorldH); + float64_t2 bottomRightTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, bottomRightWorldH); + + // Get the min and max of each lattice coordinate + float64_t2 minTop = nbl::hlsl::min(topLeftTileLattice, topRightTileLattice); + float64_t2 minBottom = nbl::hlsl::min(bottomLeftTileLattice, bottomRightTileLattice); + float64_t2 minAll = nbl::hlsl::min(minTop, minBottom); + + float64_t2 maxTop = nbl::hlsl::max(topLeftTileLattice, topRightTileLattice); + float64_t2 maxBottom = nbl::hlsl::max(bottomLeftTileLattice, bottomRightTileLattice); + float64_t2 maxAll = nbl::hlsl::max(maxTop, maxBottom); + + // Floor mins and ceil maxes + int32_t2 minAllFloored = nbl::hlsl::floor(minAll); + int32_t2 maxAllCeiled = nbl::hlsl::ceil(maxAll); + + // Clamp them to reasonable tile indices + uint32_t2 minEffective = nbl::hlsl::clamp(minAllFloored, int32_t2(0, 0), int32_t2(maxImageTileIndices)); + uint32_t2 maxEffective = nbl::hlsl::clamp(maxAllCeiled, int32_t2(0, 0), int32_t2(maxImageTileIndices)); + + // Now we have the indices of the tiles we want to upload, so create the vector of `StreamedImageCopies` - 1 per tile. + core::vector retVal; + retVal.reserve((maxEffective.x - minEffective.x + 1) * (maxEffective.y - minEffective.y + 1)); + + // Assuming a 1 pixel per block format for simplicity rn + auto bytesPerPixel = getTexelOrBlockBytesize(georeferencedImageParams.format); + auto bytesPerSide = bytesPerPixel * TileSize; + + for (uint32_t tileX = minEffective.x; tileX <= maxEffective.x; tileX++) + { + for (uint32_t tileY = minEffective.y; tileY <= maxEffective.y; tileY++) + { + asset::IImage::SBufferCopy bufCopy; + bufCopy.bufferOffset = (tileY * maxImageTileIndices.x * bytesPerSide + tileX) * bytesPerSide; + bufCopy.bufferRowLength = georeferencedImageParams.imageExtents.x; + bufCopy.bufferImageHeight = 0; + bufCopy.imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; + bufCopy.imageSubresource.mipLevel = 0u; + bufCopy.imageSubresource.baseArrayLayer = 0u; + bufCopy.imageSubresource.layerCount = 1u; + bufCopy.imageOffset = { 0u,0u,0u }; + bufCopy.imageExtent.width = TileSize; + bufCopy.imageExtent.height = TileSize; + bufCopy.imageExtent.depth = 1; + + retVal.emplace_back(georeferencedImageParams.format, georeferencedImageParams.geoReferencedImage->getBuffer(), std::move(bufCopy)); + } + } + return retVal; } \ No newline at end of file diff --git a/62_CAD/DrawResourcesFiller.h b/62_CAD/DrawResourcesFiller.h index 1a74338e7..d7d38e9f0 100644 --- a/62_CAD/DrawResourcesFiller.h +++ b/62_CAD/DrawResourcesFiller.h @@ -120,6 +120,27 @@ struct DrawResourcesFiller geometryInfo.getAlignedStorageSize(); } }; + + // @brief Used to load tiles into VRAM, keep track of loaded tiles, determine how they get sampled etc. + struct StreamedImageManager + { + friend class DrawResourcesFiller; + constexpr static uint32_t TileSize = 128u; + + StreamedImageManager(GeoreferencedImageParams&& _georeferencedImageParams); + + core::vector generateTileUploadData(const float64_t3x3& worldToNDC); + + // This and the logic they're in will likely change later with Toroidal updating + protected: + GeoreferencedImageParams georeferencedImageParams; + uint32_t2 maxResidentTiles = {}; + private: + uint32_t2 minLoadedTileIndices = {}; + uint32_t2 maxImageTileIndices = {}; + // See constructor for info on this one + float64_t2x3 offsetCoBScaleMatrix = {}; + }; DrawResourcesFiller(); @@ -343,7 +364,7 @@ struct DrawResourcesFiller * @return true if the image was successfully cached and is ready for use; false if allocation failed. * [TODO]: should be internal protected member function. */ - bool ensureGeoreferencedImageAvailability_AllocateIfNeeded(image_id imageID, const GeoreferencedImageParams& params, SIntendedSubmitInfo& intendedNextSubmit); + bool ensureGeoreferencedImageAvailability_AllocateIfNeeded(StreamedImageManager& manager, SIntendedSubmitInfo& intendedNextSubmit); // [TODO]: should be internal protected member function. bool queueGeoreferencedImageCopy_Internal(image_id imageID, const StreamedImageCopy& imageCopy); @@ -663,9 +684,9 @@ struct DrawResourcesFiller * * @param[out] outImageParams Structure to be filled with image creation parameters (format, size, etc.). * @param[out] outImageType Indicates whether the image should be fully resident or streamed. - * @param[in] georeferencedImageParams Parameters describing the full image extents, viewport extents, and format. + * @param[in] manager Manager for the georeferenced image */ - void determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, ImageType& outImageType, const GeoreferencedImageParams& georeferencedImageParams); + void determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, StreamedImageManager& manager); /** * @brief Used to implement both `drawHatch` and `drawFixedGeometryHatch` without exposing the transformation type parameter diff --git a/62_CAD/Images.h b/62_CAD/Images.h index a341eadd6..8453c93ab 100644 --- a/62_CAD/Images.h +++ b/62_CAD/Images.h @@ -28,6 +28,10 @@ struct GeoreferencedImageParams uint32_t2 imageExtents = {}; uint32_t2 viewportExtents = {}; asset::E_FORMAT format = {}; + ImageType imageType; + image_id imageID; + // For now it's going to be fully resident in memory, later on it's probably going to be a streamer class most likely. + core::smart_refctd_ptr geoReferencedImage; // TODO: Need to add other stuff later. }; @@ -205,7 +209,7 @@ class ImagesCache : public core::ResizableLRUCache struct StreamedImageCopy { asset::E_FORMAT srcFormat; - core::smart_refctd_ptr srcBuffer; // Make it 'std::future' later? + ICPUBuffer* srcBuffer; // Make it 'std::future' later? asset::IImage::SBufferCopy region; }; diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index 5cb4082bd..f9ebb83cb 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -61,6 +61,7 @@ enum class ExampleMode CASE_9, // DTM CASE_BUG, // Bug Repro, after fix, rename to CASE_10 and comment should be: testing fixed geometry and emulated fp64 corner cases CASE_11, // grid DTM + CASE_12, // Georeferenced streamed images CASE_COUNT }; @@ -77,7 +78,8 @@ constexpr std::array cameraExtents = 600.0, // CASE_8 600.0, // CASE_9 10.0, // CASE_BUG - 1000.0 // CASE_11 + 1000.0, // CASE_11 + 10.0 // CASE_12 }; constexpr ExampleMode mode = ExampleMode::CASE_11; @@ -3109,12 +3111,6 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu //printf("\n"); } - GeoreferencedImageParams geoRefParams = {}; - geoRefParams.format = asset::EF_R8G8B8A8_SRGB; - geoRefParams.imageExtents = uint32_t2 (2048, 2048); - geoRefParams.viewportExtents = (m_realFrameIx <= 5u) ? uint32_t2(1280, 720) : uint32_t2(3840, 2160); // to test trigerring resize/recreation - // drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(6996, geoRefParams, intendedNextSubmit); - LineStyleInfo lineStyle = { .color = float32_t4(1.0f, 0.1f, 0.1f, 0.9f), @@ -3698,6 +3694,22 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu } #endif } + else if (mode == ExampleMode::CASE_12) + { + for (uint32_t i = 0; i < sampleImages.size(); ++i) + { + uint64_t imageID = i * 69ull; // it can be hash or something of the file path the image was loaded from + //printf(std::format("\n Image {} \n", i).c_str()); + drawResourcesFiller.ensureStaticImageAvailability({ imageID, sampleImages[i] }, intendedNextSubmit); + drawResourcesFiller.addImageObject(imageID, { .topLeft = { 0.0 + (i) * 3.0, 0.0 }, .dirU = { 3.0 , 0.0 }, .aspectRatio = 1.0 }, intendedNextSubmit); + //printf("\n"); + } + + GeoreferencedImageParams geoRefParams = {}; + geoRefParams.format = asset::EF_R8G8B8A8_SRGB; + geoRefParams.imageExtents = uint32_t2(2048, 2048); + // drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(6996, geoRefParams, intendedNextSubmit); + } } double getScreenToWorldRatio(const float64_t3x3& viewProjectionMatrix, uint32_t2 windowSize) diff --git a/62_CAD/scripts/tiled_grid.py b/62_CAD/scripts/tiled_grid.py new file mode 100644 index 000000000..737c3463e --- /dev/null +++ b/62_CAD/scripts/tiled_grid.py @@ -0,0 +1,266 @@ +from PIL import Image, ImageDraw, ImageFont +import numpy as np +import os +import OpenImageIO as oiio + + + +def create_single_tile(tile_size, color, x_coord, y_coord, font_path=None): + """ + Creates a single square tile image with a given color and two lines of centered text. + + Args: + tile_size (int): The sidelength of the square tile in pixels. + color (tuple): A tuple of three floats (R, G, B) representing the color (0.0-1.0). + x_coord (int): The X coordinate to display on the tile. + y_coord (int): The Y coordinate to display on the tile. + font_path (str, optional): The path to a TrueType font file (.ttf). + If None, a default PIL font will be used. + Returns: + PIL.Image.Image: The created tile image with text. + """ + # Convert float color (0.0-1.0) to 8-bit integer color (0-255) + int_color = tuple(int(max(0, min(1, c)) * 255) for c in color) # Ensure color components are clamped + + img = Image.new('RGB', (tile_size, tile_size), int_color) + draw = ImageDraw.Draw(img) + + text_line1 = f"x = {x_coord}" + text_line2 = f"y = {y_coord}" + + text_fill_color = (255, 255, 255) + + # --- Dynamic Font Size Adjustment --- + # Start with a relatively large font size and shrink if needed + font_size = int(tile_size * 0.25) # Initial guess for font size + max_font_size = int(tile_size * 0.25) # Don't exceed this + + font = None + max_iterations = 100 # Prevent infinite loops in font size reduction + + for _ in range(max_iterations): + current_font_path = font_path + current_font_size = max(1, font_size) # Ensure font size is at least 1 + + try: + if current_font_path and os.path.exists(current_font_path): + font = ImageFont.truetype(current_font_path, current_font_size) + else: + # Fallback to default font (size argument might not always work perfectly) + font = ImageFont.load_default() + # For default font, try to scale if load_default(size=...) is supported and works + try: + scaled_font = ImageFont.load_default(size=current_font_size) + if draw.textbbox((0, 0), text_line1, font=scaled_font)[2] > 0: # Check if usable + font = scaled_font + except Exception: + pass # Stick with original default font + + if font is None: # Last resort if no font could be loaded + font = ImageFont.load_default() + + # Measure text dimensions + bbox1 = draw.textbbox((0, 0), text_line1, font=font) + text_width1 = bbox1[2] - bbox1[0] + text_height1 = bbox1[3] - bbox1[1] + + bbox2 = draw.textbbox((0, 0), text_line2, font=font) + text_width2 = bbox2[2] - bbox2[0] + text_height2 = bbox2[3] - bbox2[1] + + # Calculate total height needed for both lines plus some padding + # Let's assume a small gap between lines (e.g., 0.1 * text_height) + line_gap = int(text_height1 * 0.2) # 20% of line height + total_text_height = text_height1 + text_height2 + line_gap + + # Check if text fits vertically and horizontally + if (total_text_height < tile_size * 0.9) and \ + (text_width1 < tile_size * 0.9) and \ + (text_width2 < tile_size * 0.9): + break # Font size is good, break out of loop + else: + font_size -= 1 # Reduce font size + if font_size <= 0: # Prevent infinite loop if text can never fit + font_size = 1 # Smallest possible font size + break + + except Exception as e: + # Handle cases where font loading or textbbox fails + print(f"Error during font sizing: {e}. Reducing font size and retrying.") + font_size -= 1 + if font_size <= 0: + font_size = 1 + break # Cannot make font smaller, stop + + # Final check: if font_size became 0 or less, ensure it's at least 1 + if font_size <= 0: + font_size = 1 + # Reload font with minimum size if needed + if font_path and os.path.exists(font_path): + font = ImageFont.truetype(font_path, font_size) + else: + font = ImageFont.load_default() + try: + scaled_font = ImageFont.load_default(size=font_size) + if draw.textbbox((0, 0), text_line1, font=scaled_font)[2] > 0: + font = scaled_font + except Exception: + pass + + + # Re-measure with final font size to ensure accurate positioning + bbox1 = draw.textbbox((0, 0), text_line1, font=font) + text_width1 = bbox1[2] - bbox1[0] + text_height1 = bbox1[3] - bbox1[1] + + bbox2 = draw.textbbox((0, 0), text_line2, font=font) + text_width2 = bbox2[2] - bbox2[0] + text_height2 = bbox2[3] - bbox2[1] + + # Calculate positions for centering + # Line 1: centered horizontally, midpoint at 1/3 tile height + x1 = (tile_size - text_width1) / 2 + y1 = (tile_size / 3) - (text_height1 / 2) + + # Line 2: centered horizontally, midpoint at 2/3 tile height + x2 = (tile_size - text_width2) / 2 + y2 = (tile_size * 2 / 3) - (text_height2 / 2) + + # Draw the text + draw.text((x1, y1), text_line1, fill=text_fill_color, font=font) + draw.text((x2, y2), text_line2, fill=text_fill_color, font=font) + + return img + +def generate_interpolated_grid_image(tile_size, count, font_path=None): + """ + Generates a large image composed of 'count' x 'count' tiles, + with colors bilinearly interpolated from corners and text indicating tile index. + + Args: + tile_size (int): The sidelength of each individual square tile in pixels. + count (int): The number of tiles per side of the large grid (e.g., if count=3, + it's a 3x3 grid of tiles). + font_path (str, optional): Path to a TrueType font file for the tile text. + If None, a default PIL font will be used. + + Returns: + PIL.Image.Image: The generated large grid image. + """ + if count <= 0: + raise ValueError("Count must be a positive integer.") + + total_image_size = count * tile_size + main_img = Image.new('RGB', (total_image_size, total_image_size)) + + # Corner colors (R, G, B) as floats (0.0-1.0) + corner_colors = { + "top_left": (1.0, 0.0, 0.0), # Red + "top_right": (1.0, 0.0, 1.0), # Purple + "bottom_left": (0.0, 1.0, 0.0), # Green + "bottom_right": (0.0, 0.0, 1.0) # Blue + } + + # Handle the edge case where count is 1 + if count == 1: + # If count is 1, there's only one tile, which is the top-left corner + tile_color = corner_colors["top_left"] + tile_image = create_single_tile(tile_size, tile_color, 0, 0, font_path=font_path) + main_img.paste(tile_image, (0, 0)) + return main_img + + for y_tile in range(count): + for x_tile in range(count): + # Calculate normalized coordinates (u, v) for interpolation + # We divide by (count - 1) to ensure 0 and 1 values at the edges + u = x_tile / (count - 1) + v = y_tile / (count - 1) + + # Apply the simplified bilinear interpolation formulas + r_component = 1 - v + g_component = v * (1 - u) + b_component = u + + # Clamp components to be within 0.0 and 1.0 (due to potential floating point inaccuracies) + current_color = ( + max(0.0, min(1.0, r_component)), + max(0.0, min(1.0, g_component)), + max(0.0, min(1.0, b_component)) + ) + + # Create the individual tile + tile_image = create_single_tile(tile_size, current_color, x_tile, y_tile, font_path=font_path) + + # Paste the tile onto the main image + paste_x = x_tile * tile_size + paste_y = y_tile * tile_size + main_img.paste(tile_image, (paste_x, paste_y)) + + return main_img + + + + +import argparse +parser = argparse.ArgumentParser(description="Process two optional named parameters.") +parser.add_argument('--ts', type=int, default=128, help='Tile Size') +parser.add_argument('--gs', type=int, default=128, help='Grid Size') + +# Parse the arguments +args = parser.parse_args() + + +# --- Configuration --- +tile_sidelength = args.ts # Size of each individual tile in pixels +grid_count = args.gs # Number of tiles per side (e.g., 15 means 15x15 grid) + +# Path to a font file (adjust this for your system) +# On Windows, you can typically use 'C:/Windows/Fonts/arial.ttf' or similar +# You might need to find a suitable font on your system. +# For testing, you can use None to let PIL use its default font. +# If a specific font path is provided and doesn't exist, it will fall back to default. +windows_font_path = "C:/Windows/Fonts/arial.ttf" # Example path for Windows +# If Arial is not found, try Times New Roman: +# windows_font_path = "C:/Windows/Fonts/times.ttf" + +font_to_use = None +if os.name == 'nt': # Check if OS is Windows + if os.path.exists(windows_font_path): + font_to_use = windows_font_path + print(f"Using font: {windows_font_path}") + else: + print(f"Warning: Windows font not found at '{windows_font_path}'. Using default PIL font.") +else: # Assume Linux/macOS for other OS types + # Common Linux/macOS font paths (adjust as needed) + linux_font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" + mac_font_path = "/Library/Fonts/Arial.ttf" + if os.path.exists(linux_font_path): + font_to_use = linux_font_path + print(f"Using font: {linux_font_path}") + elif os.path.exists(mac_font_path): + font_to_use = mac_font_path + print(f"Using font: {mac_font_path}") + else: + print("Warning: No common Linux/macOS font found. Using default PIL font.") + + +# --- Generate and save the image --- +print(f"Generating a {grid_count}x{grid_count} grid of tiles, each {tile_sidelength}x{tile_sidelength} pixels.") +print(f"Total image size will be {grid_count * tile_sidelength}x{grid_count * tile_sidelength} pixels.") + +try: + final_image = generate_interpolated_grid_image(tile_sidelength, grid_count, font_path=font_to_use) + output_filename = "../../media/tiled_grid.exr" + np_img = np.array(final_image).astype(np.float32) / 255.0 # Normalize for EXR + spec = oiio.ImageSpec(final_image.width, final_image.height, 3, oiio.TypeDesc("float")) + out = oiio.ImageOutput.create(output_filename) + out.open(output_filename, spec) + out.write_image(np_img.reshape(-1)) # Flatten for OIIO’s expected input + out.close() + + print(f"Successfully created '{output_filename}'") + +except ValueError as e: + print(f"Error: {e}") +except Exception as e: + print(f"An unexpected error occurred: {e}") \ No newline at end of file From e523868e11ccf0747184413748b160854ecaa15b Mon Sep 17 00:00:00 2001 From: Fletterio Date: Wed, 23 Jul 2025 23:23:33 -0300 Subject: [PATCH 02/11] Off by one error fix --- 62_CAD/DrawResourcesFiller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 5c2242547..4c259220f 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -2713,14 +2713,14 @@ core::vector DrawResourcesFiller::StreamedImageManager::gener // Assuming a 1 pixel per block format for simplicity rn auto bytesPerPixel = getTexelOrBlockBytesize(georeferencedImageParams.format); - auto bytesPerSide = bytesPerPixel * TileSize; + size_t bytesPerSide = bytesPerPixel * TileSize; for (uint32_t tileX = minEffective.x; tileX <= maxEffective.x; tileX++) { for (uint32_t tileY = minEffective.y; tileY <= maxEffective.y; tileY++) { asset::IImage::SBufferCopy bufCopy; - bufCopy.bufferOffset = (tileY * maxImageTileIndices.x * bytesPerSide + tileX) * bytesPerSide; + bufCopy.bufferOffset = (tileY * (maxImageTileIndices.x + 1) * bytesPerSide + tileX) * bytesPerSide; bufCopy.bufferRowLength = georeferencedImageParams.imageExtents.x; bufCopy.bufferImageHeight = 0; bufCopy.imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; From a54f6c6b90a3d893bf6341fa3f6fcb5e2f7c7375 Mon Sep 17 00:00:00 2001 From: Fletterio Date: Wed, 23 Jul 2025 23:26:45 -0300 Subject: [PATCH 03/11] Fix tile offsets for upload --- 62_CAD/DrawResourcesFiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 4c259220f..1fbfe1bff 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -2727,7 +2727,7 @@ core::vector DrawResourcesFiller::StreamedImageManager::gener bufCopy.imageSubresource.mipLevel = 0u; bufCopy.imageSubresource.baseArrayLayer = 0u; bufCopy.imageSubresource.layerCount = 1u; - bufCopy.imageOffset = { 0u,0u,0u }; + bufCopy.imageOffset = { tileX * TileSize, tileY * TileSize, 0u }; bufCopy.imageExtent.width = TileSize; bufCopy.imageExtent.height = TileSize; bufCopy.imageExtent.depth = 1; From c3f7d04234e149b64d3bfa8cc49664a99bc38fb8 Mon Sep 17 00:00:00 2001 From: Fletterio Date: Mon, 28 Jul 2025 00:28:21 -0300 Subject: [PATCH 04/11] Skeleton done but currently bugged, some byte offset is wrong (related to VkBufferImageCopy used to upload tiles) --- 62_CAD/DrawResourcesFiller.cpp | 120 ++++++++++++++++++++------------- 62_CAD/DrawResourcesFiller.h | 17 ++++- 62_CAD/Images.h | 1 - 62_CAD/main.cpp | 33 +++++---- 4 files changed, 108 insertions(+), 63 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 1fbfe1bff..7ec6d2145 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -641,7 +641,7 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( auto evictCallback = [&](image_id imageID, const CachedImageRecord& evicted) { evictImage_SubmitIfNeeded(imageID, evicted, intendedNextSubmit); }; CachedImageRecord* cachedImageRecord = imagesCache->insert(manager.georeferencedImageParams.imageID, intendedNextSubmit.getFutureScratchSemaphore().value, evictCallback); - // TODO: Function call that gets you image creaation params based on georeferencedImageParams (extents and mips and whatever), it will also get you the GEOREFERENED TYPE + // TODO: Function call that gets you image creaation params based on georeferencedImageParams (extents and mips and whatever), it will also get you the GEOREFERENCED TYPE IGPUImage::SCreationParams imageCreationParams = {}; determineGeoreferencedImageCreationParams(imageCreationParams, manager); @@ -670,7 +670,7 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( const auto cachedImageType = cachedImageRecord->type; // image type and creation params (most importantly extent and format) should match, otherwise we evict, recreate and re-pus const auto currentParams = static_cast(imageCreationParams); - const bool needsRecreation = cachedImageType != manager.georeferencedImageParams.imageType || cachedParams != currentParams; + const bool needsRecreation = cachedImageType != manager.imageType || cachedParams != currentParams; if (needsRecreation) { // call the eviction callback so the currently cached imageID gets eventually deallocated from memory arena. @@ -708,7 +708,7 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( if (allocResults.isValid()) { - cachedImageRecord->type = manager.georeferencedImageParams.imageType; + cachedImageRecord->type = manager.imageType; cachedImageRecord->state = ImageState::CREATED_AND_MEMORY_BOUND; cachedImageRecord->lastUsedFrameIndex = currentFrameIndex; // there was an eviction + auto-submit, we need to update AGAIN cachedImageRecord->allocationOffset = allocResults.allocationOffset; @@ -866,7 +866,7 @@ void DrawResourcesFiller::addImageObject(image_id imageID, const OrientedBoundin endMainObject(); } -void DrawResourcesFiller::addGeoreferencedImage(image_id imageID, const GeoreferencedImageParams& params, SIntendedSubmitInfo& intendedNextSubmit) +void DrawResourcesFiller::addGeoreferencedImage(StreamedImageManager& manager, const float64_t3x3& NDCToWorld, SIntendedSubmitInfo& intendedNextSubmit) { beginMainObject(MainObjectType::STREAMED_IMAGE); @@ -878,11 +878,21 @@ void DrawResourcesFiller::addGeoreferencedImage(image_id imageID, const Georefer return; } + // Generate upload data + auto uploadData = manager.generateTileUploadData(NDCToWorld); + + // Queue image uploads - if necessary + if (manager.imageType == ImageType::GEOREFERENCED_STREAMED) + { + for (const auto& imageCopy : uploadData.tiles) + queueGeoreferencedImageCopy_Internal(manager.georeferencedImageParams.imageID, imageCopy); + } + GeoreferencedImageInfo info = {}; - info.topLeft = params.worldspaceOBB.topLeft; - info.dirU = params.worldspaceOBB.dirU; - info.aspectRatio = params.worldspaceOBB.aspectRatio; - info.textureID = getImageIndexFromID(imageID, intendedNextSubmit); // for this to be valid and safe, this function needs to be called immediately after `addStaticImage` function to make sure image is in memory + info.topLeft = uploadData.worldspaceOBB.topLeft; + info.dirU = uploadData.worldspaceOBB.dirU; + info.aspectRatio = uploadData.worldspaceOBB.aspectRatio; + info.textureID = getImageIndexFromID(manager.georeferencedImageParams.imageID, intendedNextSubmit); // for this to be valid and safe, this function needs to be called immediately after `addStaticImage` function to make sure image is in memory if (!addGeoreferencedImageInfo_Internal(info, mainObjIdx)) { // single image object couldn't fit into memory to push to gpu, so we submit rendering current objects and reset geometry buffer and draw objects @@ -1369,7 +1379,7 @@ bool DrawResourcesFiller::pushStaticImagesUploads(SIntendedSubmitInfo& intendedN std::vector nonResidentImageRecords; for (auto& [id, record] : imagesCache) { - if (record.staticCPUImage && record.type == ImageType::STATIC && record.state < ImageState::GPU_RESIDENT_WITH_VALID_STATIC_DATA) + if (record.staticCPUImage && (record.type == ImageType::STATIC || record.type == ImageType::GEOREFERENCED_FULL_RESOLUTION) && record.state < ImageState::GPU_RESIDENT_WITH_VALID_STATIC_DATA) nonResidentImageRecords.push_back(&record); } @@ -2469,15 +2479,15 @@ void DrawResourcesFiller::determineGeoreferencedImageCreationParams(nbl::asset:: const bool betterToResideFullyInMem = georeferencedImageParams.imageExtents.x * georeferencedImageParams.imageExtents.y <= georeferencedImageParams.viewportExtents.x * georeferencedImageParams.viewportExtents.y; if (betterToResideFullyInMem) - georeferencedImageParams.imageType = ImageType::GEOREFERENCED_FULL_RESOLUTION; + manager.imageType = ImageType::GEOREFERENCED_FULL_RESOLUTION; else - georeferencedImageParams.imageType = ImageType::GEOREFERENCED_STREAMED; + manager.imageType = ImageType::GEOREFERENCED_STREAMED; outImageParams.type = asset::IImage::ET_2D; outImageParams.samples = asset::IImage::ESCF_1_BIT; outImageParams.format = georeferencedImageParams.format; - if (georeferencedImageParams.imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) + if (manager.imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) { outImageParams.extent = { georeferencedImageParams.imageExtents.x, georeferencedImageParams.imageExtents.y, 1u }; } @@ -2648,7 +2658,7 @@ DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedIma // 1. Displacement. The following matrix calculates the offset for an input point `p` with homogenous worldspace coordinates. // By foregoing the homogenous coordinate we can keep only the vector part, that's why it's `2x3` and not `3x3` float64_t2 topLeftWorld = georeferencedImageParams.worldspaceOBB.topLeft; - float64_t2x3 displacementMatrix(1., 0., topLeftWorld.x, 0., 1., topLeftWorld.y); + float64_t2x3 displacementMatrix(1., 0., - topLeftWorld.x, 0., 1., - topLeftWorld.y); // 2. Change of Basis. Since {dirU, dirV} are orthogonal, the matrix to change from world coords to "image worldspan" coords has a quite nice expression float64_t2 dirU = georeferencedImageParams.worldspaceOBB.dirU; @@ -2669,55 +2679,68 @@ DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedIma // Put them all together offsetCoBScaleMatrix = nbl::hlsl::mul(scaleMatrix, nbl::hlsl::mul(changeOfBasisMatrix, displacementMatrix)); + + // Create a "sliding window OBB" that we use to offset tiles + fromTopLeftOBB = georeferencedImageParams.worldspaceOBB; + fromTopLeftOBB.dirU *= float32_t(TileSize * maxResidentTiles.x) / georeferencedImageParams.imageExtents.x; + // I think aspect ratio can stay the same since worldspace OBB and imageExtents should have same aspect ratio + // If the image can be stretched/sheared and not simply rotated, then the aspect ratio *might* have to change, although I think that's covered by + // the OBB's aspect ratio } -core::vector DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const float64_t3x3& NDCToWorld) +DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const float64_t3x3& NDCToWorld) { + if (imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) + return TileUploadData{ {}, georeferencedImageParams.worldspaceOBB }; + + // Following need only be done if image is actually streamed + // Using Vulkan NDC, the viewport has coordinates in the range [-1, -1] x [1,1]. First we get the world coordinates of the viewport corners, in homogenous - float64_t3 topLeftNDCH(-1., -1., 1.); - float64_t3 topRightNDCH(1., -1., 1.); - float64_t3 bottomLeftNDCH(-1., 1., 1.); - float64_t3 bottomRightNDCH(1., 1., 1.); + const float64_t3 topLeftNDCH(-1., -1., 1.); + const float64_t3 topRightNDCH(1., -1., 1.); + const float64_t3 bottomLeftNDCH(-1., 1., 1.); + const float64_t3 bottomRightNDCH(1., 1., 1.); - float64_t3 topLeftWorldH = nbl::hlsl::mul(NDCToWorld, topLeftNDCH); - float64_t3 topRightWorldH = nbl::hlsl::mul(NDCToWorld, topRightNDCH); - float64_t3 bottomLeftWorldH = nbl::hlsl::mul(NDCToWorld, bottomLeftNDCH); - float64_t3 bottomRightWorldH = nbl::hlsl::mul(NDCToWorld, bottomRightNDCH); + const float64_t3 topLeftWorldH = nbl::hlsl::mul(NDCToWorld, topLeftNDCH); + const float64_t3 topRightWorldH = nbl::hlsl::mul(NDCToWorld, topRightNDCH); + const float64_t3 bottomLeftWorldH = nbl::hlsl::mul(NDCToWorld, bottomLeftNDCH); + const float64_t3 bottomRightWorldH = nbl::hlsl::mul(NDCToWorld, bottomRightNDCH); // We can use `offsetCoBScaleMatrix` to get tile lattice coordinates for each of these points - float64_t2 topLeftTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, topLeftWorldH); - float64_t2 topRightTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, topRightWorldH); - float64_t2 bottomLeftTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, bottomLeftWorldH); - float64_t2 bottomRightTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, bottomRightWorldH); + const float64_t2 topLeftTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, topLeftWorldH); + const float64_t2 topRightTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, topRightWorldH); + const float64_t2 bottomLeftTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, bottomLeftWorldH); + const float64_t2 bottomRightTileLattice = nbl::hlsl::mul(offsetCoBScaleMatrix, bottomRightWorldH); // Get the min and max of each lattice coordinate - float64_t2 minTop = nbl::hlsl::min(topLeftTileLattice, topRightTileLattice); - float64_t2 minBottom = nbl::hlsl::min(bottomLeftTileLattice, bottomRightTileLattice); - float64_t2 minAll = nbl::hlsl::min(minTop, minBottom); + const float64_t2 minTop = nbl::hlsl::min(topLeftTileLattice, topRightTileLattice); + const float64_t2 minBottom = nbl::hlsl::min(bottomLeftTileLattice, bottomRightTileLattice); + const float64_t2 minAll = nbl::hlsl::min(minTop, minBottom); - float64_t2 maxTop = nbl::hlsl::max(topLeftTileLattice, topRightTileLattice); - float64_t2 maxBottom = nbl::hlsl::max(bottomLeftTileLattice, bottomRightTileLattice); - float64_t2 maxAll = nbl::hlsl::max(maxTop, maxBottom); + const float64_t2 maxTop = nbl::hlsl::max(topLeftTileLattice, topRightTileLattice); + const float64_t2 maxBottom = nbl::hlsl::max(bottomLeftTileLattice, bottomRightTileLattice); + const float64_t2 maxAll = nbl::hlsl::max(maxTop, maxBottom); // Floor mins and ceil maxes - int32_t2 minAllFloored = nbl::hlsl::floor(minAll); - int32_t2 maxAllCeiled = nbl::hlsl::ceil(maxAll); + const int32_t2 minAllFloored = nbl::hlsl::floor(minAll); + const int32_t2 maxAllCeiled = nbl::hlsl::ceil(maxAll); // Clamp them to reasonable tile indices - uint32_t2 minEffective = nbl::hlsl::clamp(minAllFloored, int32_t2(0, 0), int32_t2(maxImageTileIndices)); - uint32_t2 maxEffective = nbl::hlsl::clamp(maxAllCeiled, int32_t2(0, 0), int32_t2(maxImageTileIndices)); + minLoadedTileIndices = nbl::hlsl::clamp(minAllFloored, int32_t2(0, 0), int32_t2(maxImageTileIndices)); + maxLoadedTileIndices = nbl::hlsl::clamp(maxAllCeiled, int32_t2(0, 0), int32_t2(maxImageTileIndices)); // Now we have the indices of the tiles we want to upload, so create the vector of `StreamedImageCopies` - 1 per tile. - core::vector retVal; - retVal.reserve((maxEffective.x - minEffective.x + 1) * (maxEffective.y - minEffective.y + 1)); + core::vector tiles; + tiles.reserve((maxLoadedTileIndices.x - minLoadedTileIndices.x + 1) * (maxLoadedTileIndices.y - minLoadedTileIndices.y + 1)); - // Assuming a 1 pixel per block format for simplicity rn + // Assuming a 1 pixel per block format - otherwise math here gets a bit trickier auto bytesPerPixel = getTexelOrBlockBytesize(georeferencedImageParams.format); - size_t bytesPerSide = bytesPerPixel * TileSize; + const size_t bytesPerSide = bytesPerPixel * TileSize; - for (uint32_t tileX = minEffective.x; tileX <= maxEffective.x; tileX++) + // Dangerous code - assumes image can be perfectly covered with tiles. Otherwise will need to handle edge cases + for (uint32_t tileX = minLoadedTileIndices.x; tileX <= maxLoadedTileIndices.x; tileX++) { - for (uint32_t tileY = minEffective.y; tileY <= maxEffective.y; tileY++) + for (uint32_t tileY = minLoadedTileIndices.y; tileY <= maxLoadedTileIndices.y; tileY++) { asset::IImage::SBufferCopy bufCopy; bufCopy.bufferOffset = (tileY * (maxImageTileIndices.x + 1) * bytesPerSide + tileX) * bytesPerSide; @@ -2727,13 +2750,20 @@ core::vector DrawResourcesFiller::StreamedImageManager::gener bufCopy.imageSubresource.mipLevel = 0u; bufCopy.imageSubresource.baseArrayLayer = 0u; bufCopy.imageSubresource.layerCount = 1u; - bufCopy.imageOffset = { tileX * TileSize, tileY * TileSize, 0u }; + bufCopy.imageOffset = { (tileX - minLoadedTileIndices.x) * TileSize, (tileY - minLoadedTileIndices.y) * TileSize, 0u }; bufCopy.imageExtent.width = TileSize; bufCopy.imageExtent.height = TileSize; bufCopy.imageExtent.depth = 1; - retVal.emplace_back(georeferencedImageParams.format, georeferencedImageParams.geoReferencedImage->getBuffer(), std::move(bufCopy)); + tiles.emplace_back(georeferencedImageParams.format, georeferencedImageParams.geoReferencedImage->getBuffer(), std::move(bufCopy)); } } - return retVal; + + // Last, we need to figure out an obb that covers only these tiles + OrientedBoundingBox2D worldspaceOBB = fromTopLeftOBB; + const float32_t2 dirV = float32_t2(worldspaceOBB.dirU.y, -worldspaceOBB.dirU.x) * worldspaceOBB.aspectRatio; + worldspaceOBB.topLeft += worldspaceOBB.dirU * float32_t(minLoadedTileIndices.x / maxResidentTiles.x); + worldspaceOBB.topLeft += dirV * float32_t(minLoadedTileIndices.y / maxResidentTiles.y); + return TileUploadData{ std::move(tiles), worldspaceOBB }; + } \ No newline at end of file diff --git a/62_CAD/DrawResourcesFiller.h b/62_CAD/DrawResourcesFiller.h index d7d38e9f0..d54c7d3f8 100644 --- a/62_CAD/DrawResourcesFiller.h +++ b/62_CAD/DrawResourcesFiller.h @@ -129,17 +129,28 @@ struct DrawResourcesFiller StreamedImageManager(GeoreferencedImageParams&& _georeferencedImageParams); - core::vector generateTileUploadData(const float64_t3x3& worldToNDC); + struct TileUploadData + { + core::vector tiles; + OrientedBoundingBox2D worldspaceOBB; + }; + + TileUploadData generateTileUploadData(const float64_t3x3& NDCToWorld); // This and the logic they're in will likely change later with Toroidal updating protected: GeoreferencedImageParams georeferencedImageParams; uint32_t2 maxResidentTiles = {}; private: + ImageType imageType; uint32_t2 minLoadedTileIndices = {}; + uint32_t2 maxLoadedTileIndices = {}; uint32_t2 maxImageTileIndices = {}; // See constructor for info on this one float64_t2x3 offsetCoBScaleMatrix = {}; + // Wordlspace OBB that covers the top left `maxResidentTiles.x x maxResidentTiles.y` tiles of the image. + // We shift this OBB by appropriate tile offsets when loading tiles + OrientedBoundingBox2D fromTopLeftOBB = {}; }; DrawResourcesFiller(); @@ -373,7 +384,7 @@ struct DrawResourcesFiller void addImageObject(image_id imageID, const OrientedBoundingBox2D& obb, SIntendedSubmitInfo& intendedNextSubmit); // This function must be called immediately after `addStaticImage` for the same imageID. - void addGeoreferencedImage(image_id imageID, const GeoreferencedImageParams& params, SIntendedSubmitInfo& intendedNextSubmit); + void addGeoreferencedImage(StreamedImageManager& manager, const float64_t3x3& NDCToWorld, SIntendedSubmitInfo& intendedNextSubmit); /// @brief call this function before submitting to ensure all buffer and textures resourcesCollection requested via drawing calls are copied to GPU /// records copy command into intendedNextSubmit's active command buffer and might possibly submits if fails allocation on staging upload memory. @@ -620,7 +631,7 @@ struct DrawResourcesFiller bool addImageObject_Internal(const ImageObjectInfo& imageObjectInfo, uint32_t mainObjIdx);; /// Attempts to upload a georeferenced image info considering resource limitations (not accounting for the resource image added using ensureStaticImageAvailability function) - bool addGeoreferencedImageInfo_Internal(const GeoreferencedImageInfo& georeferencedImageInfo, uint32_t mainObjIdx);; + bool addGeoreferencedImageInfo_Internal(const GeoreferencedImageInfo& georeferencedImageInfo, uint32_t mainObjIdx); uint32_t getImageIndexFromID(image_id imageID, const SIntendedSubmitInfo& intendedNextSubmit); diff --git a/62_CAD/Images.h b/62_CAD/Images.h index 8453c93ab..517fc0e06 100644 --- a/62_CAD/Images.h +++ b/62_CAD/Images.h @@ -28,7 +28,6 @@ struct GeoreferencedImageParams uint32_t2 imageExtents = {}; uint32_t2 viewportExtents = {}; asset::E_FORMAT format = {}; - ImageType imageType; image_id imageID; // For now it's going to be fully resident in memory, later on it's probably going to be a streamer class most likely. core::smart_refctd_ptr geoReferencedImage; diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index f9ebb83cb..d472577b8 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -82,7 +82,7 @@ constexpr std::array cameraExtents = 10.0 // CASE_12 }; -constexpr ExampleMode mode = ExampleMode::CASE_11; +constexpr ExampleMode mode = ExampleMode::CASE_12; class Camera2D { @@ -1270,6 +1270,8 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu gridDTMHeightMap = loadImage("../../media/gridDTMHeightMap.exr"); + bigTiledGrid = loadImage("../../media/tiled_grid.exr"); + // set diagonals of cells to TOP_LEFT_TO_BOTTOM_RIGHT or BOTTOM_LEFT_TO_TOP_RIGHT randomly { // assumption is that format of the grid DTM height map is *_SRGB, I don't think we need any code to ensure that @@ -3696,19 +3698,21 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu } else if (mode == ExampleMode::CASE_12) { - for (uint32_t i = 0; i < sampleImages.size(); ++i) - { - uint64_t imageID = i * 69ull; // it can be hash or something of the file path the image was loaded from - //printf(std::format("\n Image {} \n", i).c_str()); - drawResourcesFiller.ensureStaticImageAvailability({ imageID, sampleImages[i] }, intendedNextSubmit); - drawResourcesFiller.addImageObject(imageID, { .topLeft = { 0.0 + (i) * 3.0, 0.0 }, .dirU = { 3.0 , 0.0 }, .aspectRatio = 1.0 }, intendedNextSubmit); - //printf("\n"); - } - - GeoreferencedImageParams geoRefParams = {}; - geoRefParams.format = asset::EF_R8G8B8A8_SRGB; - geoRefParams.imageExtents = uint32_t2(2048, 2048); - // drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(6996, geoRefParams, intendedNextSubmit); + GeoreferencedImageParams tiledGridParams; + auto& tiledGridCreationParams = bigTiledGrid->getCreationParameters(); + tiledGridParams.worldspaceOBB.topLeft = { 0.0, 0.0 }; + tiledGridParams.worldspaceOBB.dirU = { 10.0, 0.0 }; + tiledGridParams.worldspaceOBB.aspectRatio = 1.0; + tiledGridParams.imageExtents = { tiledGridCreationParams.extent.width, tiledGridCreationParams.extent.height}; + tiledGridParams.viewportExtents = uint32_t2{ m_window->getWidth(), m_window->getHeight() }; + tiledGridParams.format = tiledGridCreationParams.format; + tiledGridParams.imageID = 6996; + tiledGridParams.geoReferencedImage = bigTiledGrid; + + DrawResourcesFiller::StreamedImageManager tiledGridManager(std::move(tiledGridParams)); + + drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(tiledGridManager, intendedNextSubmit); + drawResourcesFiller.addGeoreferencedImage(tiledGridManager, nbl::hlsl::inverse(m_Camera.constructViewProjection()), intendedNextSubmit); } } @@ -3783,6 +3787,7 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu std::vector> sampleImages; smart_refctd_ptr gridDTMHeightMap; + smart_refctd_ptr bigTiledGrid; static constexpr char FirstGeneratedCharacter = ' '; static constexpr char LastGeneratedCharacter = '~'; From 7a5e948701d8a61b08db2c018263eaa2a51d09ce Mon Sep 17 00:00:00 2001 From: Fletterio Date: Tue, 29 Jul 2025 14:14:26 -0300 Subject: [PATCH 05/11] Fix square bytes computation --- 62_CAD/DrawResourcesFiller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 7ec6d2145..3c9e85246 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -2743,7 +2743,7 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S for (uint32_t tileY = minLoadedTileIndices.y; tileY <= maxLoadedTileIndices.y; tileY++) { asset::IImage::SBufferCopy bufCopy; - bufCopy.bufferOffset = (tileY * (maxImageTileIndices.x + 1) * bytesPerSide + tileX) * bytesPerSide; + bufCopy.bufferOffset = (tileY * (maxImageTileIndices.x + 1) * TileSize + tileX) * bytesPerSide; bufCopy.bufferRowLength = georeferencedImageParams.imageExtents.x; bufCopy.bufferImageHeight = 0; bufCopy.imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; From 52d947deb94432c58de00bfb252f73ae7da4795e Mon Sep 17 00:00:00 2001 From: Fletterio Date: Wed, 30 Jul 2025 03:04:40 -0300 Subject: [PATCH 06/11] Checkpoint 1! --- 62_CAD/DrawResourcesFiller.cpp | 50 ++++++++++++++++++---------------- 62_CAD/main.cpp | 6 ++-- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 3c9e85246..e129e13a1 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -2494,11 +2494,19 @@ void DrawResourcesFiller::determineGeoreferencedImageCreationParams(nbl::asset:: else { // Pad sides to multiple of tileSize. Even after rounding up, we might still need to add an extra tile to cover both sides. - const auto xExtent = core::roundUp(georeferencedImageParams.viewportExtents.x, manager.TileSize) + manager.TileSize; - const auto yExtent = core::roundUp(georeferencedImageParams.viewportExtents.y, manager.TileSize) + manager.TileSize; + // I added two to be safe and to have issues at the borders. + const auto xExtent = core::roundUp(georeferencedImageParams.viewportExtents.x, manager.TileSize) + 2 * manager.TileSize; + const auto yExtent = core::roundUp(georeferencedImageParams.viewportExtents.y, manager.TileSize) + 2 * manager.TileSize; outImageParams.extent = { xExtent, yExtent, 1u }; manager.maxResidentTiles.x = xExtent / manager.TileSize; manager.maxResidentTiles.y = yExtent / manager.TileSize; + // Create a "sliding window OBB" that we use to offset tiles + manager.fromTopLeftOBB.topLeft = georeferencedImageParams.worldspaceOBB.topLeft; + manager.fromTopLeftOBB.dirU = georeferencedImageParams.worldspaceOBB.dirU * float32_t(manager.TileSize * manager.maxResidentTiles.x) / float32_t(georeferencedImageParams.imageExtents.x); + manager.fromTopLeftOBB.aspectRatio = float32_t(manager.maxResidentTiles.y) / float32_t(manager.maxResidentTiles.x); + // I think aspect ratio can stay the same since worldspace OBB and imageExtents should have same aspect ratio. + // If the image can be stretched/sheared and not simply rotated, then the aspect ratio *might* have to change, although I think that's covered by + // the OBB's aspect ratio } @@ -2655,12 +2663,13 @@ DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedIma // 3. Map world units to tile units. This scaling is generally nonuniform, since it depends on the ratio of pixels to world units per coordinate. // The name of the `offsetCoBScaleMatrix` follows by what is computed at each step - // 1. Displacement. The following matrix calculates the offset for an input point `p` with homogenous worldspace coordinates. + // 1. Displacement. The following matrix computes the offset for an input point `p` with homogenous worldspace coordinates. // By foregoing the homogenous coordinate we can keep only the vector part, that's why it's `2x3` and not `3x3` float64_t2 topLeftWorld = georeferencedImageParams.worldspaceOBB.topLeft; float64_t2x3 displacementMatrix(1., 0., - topLeftWorld.x, 0., 1., - topLeftWorld.y); - // 2. Change of Basis. Since {dirU, dirV} are orthogonal, the matrix to change from world coords to "image worldspan" coords has a quite nice expression + // 2. Change of Basis. Since {dirU, dirV} are orthogonal, the matrix to change from world coords to `span{dirU, dirV}` coords has a quite nice expression + // Non-uniform scaling doesn't affect this, but this has to change if we allow for shearing (basis vectors stop being orthogonal) float64_t2 dirU = georeferencedImageParams.worldspaceOBB.dirU; float64_t2 dirV = float32_t2(dirU.y, -dirU.x) * georeferencedImageParams.worldspaceOBB.aspectRatio; float64_t dirULengthSquared = nbl::hlsl::dot(dirU, dirU); @@ -2669,23 +2678,14 @@ DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedIma float64_t2 secondRow = dirV / dirVLengthSquared; float64_t2x2 changeOfBasisMatrix(firstRow, secondRow); - // 3. Scaling. The vector obtained by doing `CoB * displacement * p` is still in world units. Given that we know how many pixels the image spans (given by - // georeferencedImageParams.imageExtents) and how many world units it spans (given by (|dirU|, |dirV|) ) we can get a factor for the `pixel/world unit` ratio. - // Then we simply multiply that factor for another factor for the `tile / pixel` ratio to get our `tile / world unit` scaling factor. - float64_t dirULength = nbl::hlsl::sqrt(dirULengthSquared); - float64_t dirVLength = nbl::hlsl::sqrt(dirVLengthSquared); - float64_t2 scaleFactors = (1. / TileSize) * (float64_t2(georeferencedImageParams.imageExtents) / float64_t2(dirULength, dirVLength)); - float64_t2x2 scaleMatrix(scaleFactors.x, 0., 0., scaleFactors.y); + // 3. Scaling. The vector obtained by doing `CoB * displacement * p` are now the coordinates in the `span{dirU, dirV}`, which would be `uv` coordinates in [0,1]^2 + // (or outside this range for points not in the image). To get tile lattice coordinates, we need to scale this number by an nTiles vector which counts + // (fractionally) how many tiles fit in the image along each axis + float32_t2 nTiles = float32_t2(georeferencedImageParams.imageExtents) / float32_t2(TileSize, TileSize); + float64_t2x2 scaleMatrix(nTiles.x, 0., 0., nTiles.y); // Put them all together offsetCoBScaleMatrix = nbl::hlsl::mul(scaleMatrix, nbl::hlsl::mul(changeOfBasisMatrix, displacementMatrix)); - - // Create a "sliding window OBB" that we use to offset tiles - fromTopLeftOBB = georeferencedImageParams.worldspaceOBB; - fromTopLeftOBB.dirU *= float32_t(TileSize * maxResidentTiles.x) / georeferencedImageParams.imageExtents.x; - // I think aspect ratio can stay the same since worldspace OBB and imageExtents should have same aspect ratio - // If the image can be stretched/sheared and not simply rotated, then the aspect ratio *might* have to change, although I think that's covered by - // the OBB's aspect ratio } DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const float64_t3x3& NDCToWorld) @@ -2721,13 +2721,13 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S const float64_t2 maxBottom = nbl::hlsl::max(bottomLeftTileLattice, bottomRightTileLattice); const float64_t2 maxAll = nbl::hlsl::max(maxTop, maxBottom); - // Floor mins and ceil maxes + // Floor them to get an integer for the tiles they're in const int32_t2 minAllFloored = nbl::hlsl::floor(minAll); - const int32_t2 maxAllCeiled = nbl::hlsl::ceil(maxAll); + const int32_t2 maxAllFloored = nbl::hlsl::floor(maxAll); // Clamp them to reasonable tile indices minLoadedTileIndices = nbl::hlsl::clamp(minAllFloored, int32_t2(0, 0), int32_t2(maxImageTileIndices)); - maxLoadedTileIndices = nbl::hlsl::clamp(maxAllCeiled, int32_t2(0, 0), int32_t2(maxImageTileIndices)); + maxLoadedTileIndices = nbl::hlsl::clamp(maxAllFloored, int32_t2(0, 0), nbl::hlsl::min(int32_t2(maxImageTileIndices), int32_t2(minLoadedTileIndices + maxResidentTiles - uint32_t2(1,1)))); // Now we have the indices of the tiles we want to upload, so create the vector of `StreamedImageCopies` - 1 per tile. core::vector tiles; @@ -2759,11 +2759,13 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S } } - // Last, we need to figure out an obb that covers only these tiles + // Last, we need to figure out an obb that covers only the currently loaded tiles + // By shifting the `fromTopLeftOBB` an appropriate number of tiles in each direction, we get an obb that covers at least the uploaded tiles + // It might cover more tiles, possible some that are not even loaded into VRAM, but since those fall outside of the viewport we don't really care about them OrientedBoundingBox2D worldspaceOBB = fromTopLeftOBB; const float32_t2 dirV = float32_t2(worldspaceOBB.dirU.y, -worldspaceOBB.dirU.x) * worldspaceOBB.aspectRatio; - worldspaceOBB.topLeft += worldspaceOBB.dirU * float32_t(minLoadedTileIndices.x / maxResidentTiles.x); - worldspaceOBB.topLeft += dirV * float32_t(minLoadedTileIndices.y / maxResidentTiles.y); + worldspaceOBB.topLeft += worldspaceOBB.dirU * float32_t(minLoadedTileIndices.x) / float32_t(maxResidentTiles.x); + worldspaceOBB.topLeft += dirV * float32_t(minLoadedTileIndices.y) / float32_t(maxResidentTiles.y); return TileUploadData{ std::move(tiles), worldspaceOBB }; } \ No newline at end of file diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index d472577b8..838833c2c 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -1320,7 +1320,8 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - m_Camera.mouseProcess(events); + if (m_window->hasMouseFocus()) + m_Camera.mouseProcess(events); } , m_logger.get()); keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void @@ -3701,7 +3702,7 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu GeoreferencedImageParams tiledGridParams; auto& tiledGridCreationParams = bigTiledGrid->getCreationParameters(); tiledGridParams.worldspaceOBB.topLeft = { 0.0, 0.0 }; - tiledGridParams.worldspaceOBB.dirU = { 10.0, 0.0 }; + tiledGridParams.worldspaceOBB.dirU = { 128.0, 0.0 }; tiledGridParams.worldspaceOBB.aspectRatio = 1.0; tiledGridParams.imageExtents = { tiledGridCreationParams.extent.width, tiledGridCreationParams.extent.height}; tiledGridParams.viewportExtents = uint32_t2{ m_window->getWidth(), m_window->getHeight() }; @@ -3712,6 +3713,7 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu DrawResourcesFiller::StreamedImageManager tiledGridManager(std::move(tiledGridParams)); drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(tiledGridManager, intendedNextSubmit); + drawResourcesFiller.addGeoreferencedImage(tiledGridManager, nbl::hlsl::inverse(m_Camera.constructViewProjection()), intendedNextSubmit); } } From 665559b0c94966306f5e90c31ba731e68a4d893b Mon Sep 17 00:00:00 2001 From: Fletterio Date: Thu, 31 Jul 2025 10:34:57 -0300 Subject: [PATCH 07/11] Save before merge --- 62_CAD/DrawResourcesFiller.cpp | 2 +- 62_CAD/main.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index e129e13a1..2bc5865bd 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -714,7 +714,7 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( cachedImageRecord->allocationOffset = allocResults.allocationOffset; cachedImageRecord->allocationSize = allocResults.allocationSize; cachedImageRecord->gpuImageView = allocResults.gpuImageView; - cachedImageRecord->staticCPUImage = manager.georeferencedImageParams.geoReferencedImage; + cachedImageRecord->staticCPUImage = nullptr; } else { diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index 838833c2c..5cb84c88f 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -132,7 +132,7 @@ class Camera2D if (ev.type == nbl::ui::SMouseEvent::EET_SCROLL) { - m_bounds = m_bounds + float64_t2{ (double)ev.scrollEvent.verticalScroll * -0.1 * m_aspectRatio, (double)ev.scrollEvent.verticalScroll * -0.1}; + m_bounds = m_bounds + float64_t2{ (double)ev.scrollEvent.verticalScroll * -0.0025 * m_aspectRatio, (double)ev.scrollEvent.verticalScroll * -0.0025}; m_bounds = float64_t2{ core::max(m_aspectRatio, m_bounds.x), core::max(1.0, m_bounds.y) }; } } @@ -3702,7 +3702,7 @@ class ComputerAidedDesign final : public examples::SimpleWindowedApplication, pu GeoreferencedImageParams tiledGridParams; auto& tiledGridCreationParams = bigTiledGrid->getCreationParameters(); tiledGridParams.worldspaceOBB.topLeft = { 0.0, 0.0 }; - tiledGridParams.worldspaceOBB.dirU = { 128.0, 0.0 }; + tiledGridParams.worldspaceOBB.dirU = { 100.0 / nbl::hlsl::sqrt(2.0), 100.0 / nbl::hlsl::sqrt(2.0) }; tiledGridParams.worldspaceOBB.aspectRatio = 1.0; tiledGridParams.imageExtents = { tiledGridCreationParams.extent.width, tiledGridCreationParams.extent.height}; tiledGridParams.viewportExtents = uint32_t2{ m_window->getWidth(), m_window->getHeight() }; From fc2d504c8df2ddcb829208f62b5636fdc114e0a8 Mon Sep 17 00:00:00 2001 From: Fletterio Date: Fri, 8 Aug 2025 01:54:22 -0300 Subject: [PATCH 08/11] Bug: using uploaded uvs seems to stretch/not shrink along v direction --- 62_CAD/DrawResourcesFiller.cpp | 4 ++-- 62_CAD/shaders/main_pipeline/vertex_shader.hlsl | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 2a72305c7..392f7214c 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -2794,7 +2794,7 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S const float32_t2 dirV = float32_t2(worldspaceOBB.dirU.y, -worldspaceOBB.dirU.x) * worldspaceOBB.aspectRatio; worldspaceOBB.topLeft += worldspaceOBB.dirU * float32_t(minLoadedTileIndices.x) / float32_t(maxResidentTiles.x); worldspaceOBB.topLeft += dirV * float32_t(minLoadedTileIndices.y) / float32_t(maxResidentTiles.y); - + // Compute minUV, maxUV // Since right now we don't shift the obb around, minUV will always be (0,0), but this is bound to change later on (shifting obb will happen when we want to reuse tiles and not // reupload them on every frame in the next phase) @@ -2820,7 +2820,7 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S worldspaceOBB.dirU *= maxUV.x; // Scale the aspect ratio by the relative shrinkage of U,V. Remember our aspect ratio is V / U. worldspaceOBB.aspectRatio *= maxUV.y / maxUV.x; - + return TileUploadData{ std::move(tiles), worldspaceOBB, minUV, maxUV }; } \ No newline at end of file diff --git a/62_CAD/shaders/main_pipeline/vertex_shader.hlsl b/62_CAD/shaders/main_pipeline/vertex_shader.hlsl index 23a4c69ed..299c87778 100644 --- a/62_CAD/shaders/main_pipeline/vertex_shader.hlsl +++ b/62_CAD/shaders/main_pipeline/vertex_shader.hlsl @@ -746,6 +746,9 @@ PSInput vtxMain(uint vertexID : SV_VertexID) const float32_t2 minUV = vk::RawBufferLoad(globals.pointers.geometryBuffer + drawObj.geometryAddress + sizeof(pfloat64_t2) + sizeof(float32_t2) + sizeof(float32_t) + sizeof(uint32_t), 4u); const float32_t2 maxUV = vk::RawBufferLoad(globals.pointers.geometryBuffer + drawObj.geometryAddress + sizeof(pfloat64_t2) + 2 * sizeof(float32_t2) + sizeof(float32_t) + sizeof(uint32_t), 4u); + //printf("%f %f", minUV.x, minUV.y); + //printf("%f %f", maxUV.x, maxUV.y); + const float32_t2 dirV = float32_t2(dirU.y, -dirU.x) * aspectRatio; const float32_t2 ndcTopLeft = _static_cast(transformPointNdc(clipProjectionData.projectionToNDC, topLeft)); const float32_t2 ndcDirU = _static_cast(transformVectorNdc(clipProjectionData.projectionToNDC, _static_cast(dirU))); @@ -754,7 +757,8 @@ PSInput vtxMain(uint vertexID : SV_VertexID) const uint32_t2 corner = uint32_t2(vertexIdx & 0x1u, vertexIdx & 0x2u); const float32_t2 ndcCorner = ndcTopLeft + corner.x * ndcDirU + corner.y * ndcDirV; - const float32_t2 uv = float32_t2(corner.x ? minUV.x : maxUV.x , corner.y ? minUV.y : maxUV.y); + const float32_t2 uv = corner;// float32_t2(corner.x ? maxUV.x : minUV.x, corner.y ? maxUV.y : minUV.y); + printf("%f %f", ndcCorner.x, ndcCorner.y); outV.position = float4(ndcCorner, 0.f, 1.f); outV.setImageUV(uv); From e61e389ed763f9d9b6b44b075de00baa220763a5 Mon Sep 17 00:00:00 2001 From: Fletterio Date: Fri, 8 Aug 2025 02:13:07 -0300 Subject: [PATCH 09/11] Fixed y-axis bug --- 62_CAD/shaders/main_pipeline/vertex_shader.hlsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/62_CAD/shaders/main_pipeline/vertex_shader.hlsl b/62_CAD/shaders/main_pipeline/vertex_shader.hlsl index 299c87778..9842e67b0 100644 --- a/62_CAD/shaders/main_pipeline/vertex_shader.hlsl +++ b/62_CAD/shaders/main_pipeline/vertex_shader.hlsl @@ -754,10 +754,10 @@ PSInput vtxMain(uint vertexID : SV_VertexID) const float32_t2 ndcDirU = _static_cast(transformVectorNdc(clipProjectionData.projectionToNDC, _static_cast(dirU))); const float32_t2 ndcDirV = _static_cast(transformVectorNdc(clipProjectionData.projectionToNDC, _static_cast(dirV))); - const uint32_t2 corner = uint32_t2(vertexIdx & 0x1u, vertexIdx & 0x2u); + const bool2 corner = bool2(vertexIdx & 0x1u, vertexIdx & 0x2u); const float32_t2 ndcCorner = ndcTopLeft + corner.x * ndcDirU + corner.y * ndcDirV; - const float32_t2 uv = corner;// float32_t2(corner.x ? maxUV.x : minUV.x, corner.y ? maxUV.y : minUV.y); + const float32_t2 uv = float32_t2(corner.x ? maxUV.x : minUV.x, corner.y ? maxUV.y : minUV.y); printf("%f %f", ndcCorner.x, ndcCorner.y); outV.position = float4(ndcCorner, 0.f, 1.f); From 258920c467906763fe5866dec1fc21ba85616ddf Mon Sep 17 00:00:00 2001 From: Fletterio Date: Sun, 10 Aug 2025 19:37:00 -0300 Subject: [PATCH 10/11] Diagonal computation --- 62_CAD/DrawResourcesFiller.cpp | 84 ++++++++++++++++++---------------- 62_CAD/DrawResourcesFiller.h | 18 ++++---- 62_CAD/Images.h | 1 - 62_CAD/main.cpp | 6 +-- 4 files changed, 57 insertions(+), 52 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 392f7214c..547feca64 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -651,7 +651,7 @@ bool DrawResourcesFiller::ensureMultipleStaticImagesAvailability(std::spangetLogicalDevice(); auto* physDev = m_utilities->getLogicalDevice()->getPhysicalDevice(); @@ -659,11 +659,11 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( // Try inserting or updating the image usage in the cache. // If the image is already present, updates its semaphore value. auto evictCallback = [&](image_id imageID, const CachedImageRecord& evicted) { evictImage_SubmitIfNeeded(imageID, evicted, intendedNextSubmit); }; - CachedImageRecord* cachedImageRecord = imagesCache->insert(manager.georeferencedImageParams.imageID, intendedNextSubmit.getFutureScratchSemaphore().value, evictCallback); + CachedImageRecord* cachedImageRecord = imagesCache->insert(imageID, intendedNextSubmit.getFutureScratchSemaphore().value, evictCallback); // TODO: Function call that gets you image creaation params based on georeferencedImageParams (extents and mips and whatever), it will also get you the GEOREFERENCED TYPE IGPUImage::SCreationParams imageCreationParams = {}; - determineGeoreferencedImageCreationParams(imageCreationParams, manager); + ImageType imageType = determineGeoreferencedImageCreationParams(imageCreationParams, params); // imageParams = cpuImage->getCreationParameters(); imageCreationParams.usage |= IGPUImage::EUF_TRANSFER_DST_BIT|IGPUImage::EUF_SAMPLED_BIT; @@ -690,11 +690,11 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( const auto cachedImageType = cachedImageRecord->type; // image type and creation params (most importantly extent and format) should match, otherwise we evict, recreate and re-pus const auto currentParams = static_cast(imageCreationParams); - const bool needsRecreation = cachedImageType != manager.imageType || cachedParams != currentParams; + const bool needsRecreation = cachedImageType != imageType || cachedParams != currentParams; if (needsRecreation) { // call the eviction callback so the currently cached imageID gets eventually deallocated from memory arena. - evictCallback(manager.georeferencedImageParams.imageID, *cachedImageRecord); + evictCallback(imageID, *cachedImageRecord); // instead of erasing and inserting the imageID into the cache, we just reset it, so the next block of code goes into array index allocation + creating our new image *cachedImageRecord = CachedImageRecord(currentFrameIndex); @@ -724,11 +724,11 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( if (cachedImageRecord->arrayIndex != video::SubAllocatedDescriptorSet::AddressAllocator::invalid_address) { // Attempt to create a GPU image and image view for this texture. - ImageAllocateResults allocResults = tryCreateAndAllocateImage_SubmitIfNeeded(imageCreationParams, asset::E_FORMAT::EF_COUNT, intendedNextSubmit, std::to_string(manager.georeferencedImageParams.imageID)); + ImageAllocateResults allocResults = tryCreateAndAllocateImage_SubmitIfNeeded(imageCreationParams, asset::E_FORMAT::EF_COUNT, intendedNextSubmit, std::to_string(imageID)); if (allocResults.isValid()) { - cachedImageRecord->type = manager.imageType; + cachedImageRecord->type = imageType; cachedImageRecord->state = ImageState::CREATED_AND_MEMORY_BOUND; cachedImageRecord->lastUsedFrameIndex = currentFrameIndex; // there was an eviction + auto-submit, we need to update AGAIN cachedImageRecord->allocationOffset = allocResults.allocationOffset; @@ -762,7 +762,7 @@ bool DrawResourcesFiller::ensureGeoreferencedImageAvailability_AllocateIfNeeded( } // erase the entry we failed to fill, no need for `evictImage_SubmitIfNeeded`, because it didn't get to be used in any submit to defer it's memory and index deallocation - imagesCache->erase(manager.georeferencedImageParams.imageID); + imagesCache->erase(imageID); } } else @@ -896,21 +896,28 @@ void DrawResourcesFiller::addGeoreferencedImage(StreamedImageManager& manager, c return; } + // Query imageType + auto cachedImageRecord = imagesCache->peek(manager.imageID); + + manager.maxResidentTiles.x = cachedImageRecord->gpuImageView->getCreationParameters().image->getCreationParameters().extent.width / manager.TileSize; + manager.maxResidentTiles.y = manager.maxResidentTiles.x; + // Create a "sliding window OBB" that we use to offset tiles + manager.fromTopLeftOBB.topLeft = manager.georeferencedImageParams.worldspaceOBB.topLeft; + manager.fromTopLeftOBB.dirU = manager.georeferencedImageParams.worldspaceOBB.dirU * float32_t(manager.TileSize * manager.maxResidentTiles.x) / float32_t(manager.georeferencedImageParams.imageExtents.x); + manager.fromTopLeftOBB.aspectRatio = float32_t(manager.maxResidentTiles.y) / float32_t(manager.maxResidentTiles.x); + // Generate upload data - auto uploadData = manager.generateTileUploadData(NDCToWorld); + auto uploadData = manager.generateTileUploadData(cachedImageRecord->type, NDCToWorld); - // Queue image uploads - if necessary - if (manager.imageType == ImageType::GEOREFERENCED_STREAMED) - { - for (const auto& imageCopy : uploadData.tiles) - queueGeoreferencedImageCopy_Internal(manager.georeferencedImageParams.imageID, imageCopy); - } + // Queue image uploads + for (const auto& imageCopy : uploadData.tiles) + queueGeoreferencedImageCopy_Internal(manager.imageID, imageCopy); GeoreferencedImageInfo info = {}; info.topLeft = uploadData.worldspaceOBB.topLeft; info.dirU = uploadData.worldspaceOBB.dirU; info.aspectRatio = uploadData.worldspaceOBB.aspectRatio; - info.textureID = getImageIndexFromID(manager.georeferencedImageParams.imageID, intendedNextSubmit); // for this to be valid and safe, this function needs to be called immediately after `addStaticImage` function to make sure image is in memory + info.textureID = getImageIndexFromID(manager.imageID, intendedNextSubmit); // for this to be valid and safe, this function needs to be called immediately after `addStaticImage` function to make sure image is in memory info.minUV = uploadData.minUV; info.maxUV = uploadData.maxUV; if (!addGeoreferencedImageInfo_Internal(info, mainObjIdx)) @@ -2497,48 +2504,45 @@ DrawResourcesFiller::ImageAllocateResults DrawResourcesFiller::tryCreateAndAlloc return ret; } -void DrawResourcesFiller::determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, StreamedImageManager& manager) +ImageType DrawResourcesFiller::determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, const GeoreferencedImageParams& params) { - auto& georeferencedImageParams = manager.georeferencedImageParams; // Decide whether the image can reside fully into memory rather than get streamed. // TODO: Improve logic, currently just a simple check to see if the full-screen image has more pixels that viewport or not // TODO: add criterial that the size of the full-res image shouldn't consume more than 30% of the total memory arena for images (if we allowed larger than viewport extents) - const bool betterToResideFullyInMem = georeferencedImageParams.imageExtents.x * georeferencedImageParams.imageExtents.y <= georeferencedImageParams.viewportExtents.x * georeferencedImageParams.viewportExtents.y; + const bool betterToResideFullyInMem = params.imageExtents.x * params.imageExtents.y <= params.viewportExtents.x * params.viewportExtents.y; + + ImageType imageType; if (betterToResideFullyInMem) - manager.imageType = ImageType::GEOREFERENCED_FULL_RESOLUTION; + imageType = ImageType::GEOREFERENCED_FULL_RESOLUTION; else - manager.imageType = ImageType::GEOREFERENCED_STREAMED; + imageType = ImageType::GEOREFERENCED_STREAMED; outImageParams.type = asset::IImage::ET_2D; outImageParams.samples = asset::IImage::ESCF_1_BIT; - outImageParams.format = georeferencedImageParams.format; + outImageParams.format = params.format; - if (manager.imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) + if (imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) { - outImageParams.extent = { georeferencedImageParams.imageExtents.x, georeferencedImageParams.imageExtents.y, 1u }; + outImageParams.extent = { params.imageExtents.x, params.imageExtents.y, 1u }; } else { // Enough to cover twice the viewport at mip 0 (so that when zooming out to mip 1 the whole viewport still gets covered with mip 0 tiles) // and in any rotation (taking the longest side suffices). Can be increased to avoid frequent tile eviction when moving the camera at mip close to 1 - const uint32_t longestSide = nbl::hlsl::max(georeferencedImageParams.viewportExtents.x, georeferencedImageParams.viewportExtents.y); - const uint32_t gpuImageSidelength = 2 * (core::roundUp(longestSide, manager.TileSize) + manager.TileSize); + const uint32_t diagonal = static_cast(nbl::hlsl::ceil( + nbl::hlsl::sqrt(static_cast(params.viewportExtents.x * params.viewportExtents.x + + params.viewportExtents.y * params.viewportExtents.y)) + ) + ); + const uint32_t gpuImageSidelength = 2 * core::roundUp(diagonal, StreamedImageManager::TileSize) + StreamedImageManager::PaddingTiles * StreamedImageManager::TileSize; outImageParams.extent = { gpuImageSidelength, gpuImageSidelength, 1u }; - manager.maxResidentTiles.x = gpuImageSidelength / manager.TileSize; - manager.maxResidentTiles.y = manager.maxResidentTiles.x; - // Create a "sliding window OBB" that we use to offset tiles - manager.fromTopLeftOBB.topLeft = georeferencedImageParams.worldspaceOBB.topLeft; - manager.fromTopLeftOBB.dirU = georeferencedImageParams.worldspaceOBB.dirU * float32_t(manager.TileSize * manager.maxResidentTiles.x) / float32_t(georeferencedImageParams.imageExtents.x); - manager.fromTopLeftOBB.aspectRatio = float32_t(manager.maxResidentTiles.y) / float32_t(manager.maxResidentTiles.x); - // I think aspect ratio can stay the same since worldspace OBB and imageExtents should have same aspect ratio. - // If the image can be stretched/sheared and not simply rotated, then the aspect ratio *might* have to change, although I think that's covered by - // the OBB's aspect ratio } - outImageParams.mipLevels = 1u; // TODO: Later do mipmapping outImageParams.arrayLayers = 1u; + + return imageType; } void DrawResourcesFiller::setGlyphMSDFTextureFunction(const GetGlyphMSDFTextureFunc& func) @@ -2675,8 +2679,8 @@ void DrawResourcesFiller::flushDrawObjects() } } -DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedImageParams&& _georeferencedImageParams) - : georeferencedImageParams(std::move(_georeferencedImageParams)) +DrawResourcesFiller::StreamedImageManager::StreamedImageManager(image_id _imageID, GeoreferencedImageParams&& _georeferencedImageParams) + : imageID(_imageID), georeferencedImageParams(std::move(_georeferencedImageParams)) { maxImageTileIndices = georeferencedImageParams.imageExtents / uint32_t2(TileSize, TileSize); // If it fits perfectly along any dimension, we need one less tile with this scheme @@ -2715,10 +2719,10 @@ DrawResourcesFiller::StreamedImageManager::StreamedImageManager(GeoreferencedIma world2Tile = nbl::hlsl::mul(scaleMatrix, nbl::hlsl::mul(changeOfBasisMatrix, displacementMatrix)); } -DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const float64_t3x3& NDCToWorld) +DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const ImageType imageType, const float64_t3x3& NDCToWorld) { // I think eventually it's better to just transform georeferenced images that aren't big enough into static images and forget about them - if (imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) + if (imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) //Pass imageID as parameter, down from the addGeoRef call return TileUploadData{ {}, georeferencedImageParams.worldspaceOBB }; // Following need only be done if image is actually streamed diff --git a/62_CAD/DrawResourcesFiller.h b/62_CAD/DrawResourcesFiller.h index 74a503549..6d29e9637 100644 --- a/62_CAD/DrawResourcesFiller.h +++ b/62_CAD/DrawResourcesFiller.h @@ -18,6 +18,8 @@ static_assert(sizeof(DrawObject) == 16u); static_assert(sizeof(MainObject) == 20u); static_assert(sizeof(LineStyle) == 88u); +//TODO[Francisco]: Update briefs for geotex related functions + // ! DrawResourcesFiller // ! This class provides important functionality to manage resources needed for a draw. // ! Drawing new objects (polylines, hatches, etc.) should go through this function. @@ -126,8 +128,9 @@ struct DrawResourcesFiller { friend class DrawResourcesFiller; constexpr static uint32_t TileSize = 128u; + constexpr static uint32_t PaddingTiles = 2; - StreamedImageManager(GeoreferencedImageParams&& _georeferencedImageParams); + StreamedImageManager(image_id _imageID, GeoreferencedImageParams&& _georeferencedImageParams); struct TileUploadData { @@ -137,14 +140,13 @@ struct DrawResourcesFiller float32_t2 maxUV; }; - TileUploadData generateTileUploadData(const float64_t3x3& NDCToWorld); + TileUploadData generateTileUploadData(const ImageType imageType, const float64_t3x3& NDCToWorld); - // This and the logic they're in will likely change later with Toroidal updating - protected: + image_id imageID; GeoreferencedImageParams georeferencedImageParams; - uint32_t2 maxResidentTiles = {}; + // This and the logic they're in will likely change later with Toroidal updating private: - ImageType imageType; + uint32_t2 maxResidentTiles = {}; uint32_t2 minLoadedTileIndices = {}; uint32_t2 maxLoadedTileIndices = {}; uint32_t2 maxImageTileIndices = {}; @@ -375,7 +377,7 @@ struct DrawResourcesFiller * @return true if the image was successfully cached and is ready for use; false if allocation failed. * [TODO]: should be internal protected member function. */ - bool ensureGeoreferencedImageAvailability_AllocateIfNeeded(StreamedImageManager& manager, SIntendedSubmitInfo& intendedNextSubmit); + bool ensureGeoreferencedImageAvailability_AllocateIfNeeded(image_id imageID, const GeoreferencedImageParams& params, SIntendedSubmitInfo& intendedNextSubmit); // [TODO]: should be internal protected member function. bool queueGeoreferencedImageCopy_Internal(image_id imageID, const StreamedImageCopy& imageCopy); @@ -695,7 +697,7 @@ struct DrawResourcesFiller * @param[out] outImageType Indicates whether the image should be fully resident or streamed. * @param[in] manager Manager for the georeferenced image */ - void determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, StreamedImageManager& manager); + ImageType determineGeoreferencedImageCreationParams(nbl::asset::IImage::SCreationParams& outImageParams, const GeoreferencedImageParams& params); /** * @brief Used to implement both `drawHatch` and `drawFixedGeometryHatch` without exposing the transformation type parameter diff --git a/62_CAD/Images.h b/62_CAD/Images.h index 517fc0e06..5329a5bda 100644 --- a/62_CAD/Images.h +++ b/62_CAD/Images.h @@ -28,7 +28,6 @@ struct GeoreferencedImageParams uint32_t2 imageExtents = {}; uint32_t2 viewportExtents = {}; asset::E_FORMAT format = {}; - image_id imageID; // For now it's going to be fully resident in memory, later on it's probably going to be a streamer class most likely. core::smart_refctd_ptr geoReferencedImage; // TODO: Need to add other stuff later. diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index 540f7ec86..e420a81af 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -3687,6 +3687,7 @@ class ComputerAidedDesign final : public nbl::examples::SimpleWindowedApplicatio } else if (mode == ExampleMode::CASE_12) { + image_id tiledGridID = 6996; GeoreferencedImageParams tiledGridParams; auto& tiledGridCreationParams = bigTiledGrid->getCreationParameters(); // Position at topLeft viewport @@ -3703,12 +3704,11 @@ class ComputerAidedDesign final : public nbl::examples::SimpleWindowedApplicatio tiledGridParams.imageExtents = { tiledGridCreationParams.extent.width, tiledGridCreationParams.extent.height}; tiledGridParams.viewportExtents = uint32_t2{ m_window->getWidth(), m_window->getHeight() }; tiledGridParams.format = tiledGridCreationParams.format; - tiledGridParams.imageID = 6996; tiledGridParams.geoReferencedImage = bigTiledGrid; - DrawResourcesFiller::StreamedImageManager tiledGridManager(std::move(tiledGridParams)); + DrawResourcesFiller::StreamedImageManager tiledGridManager(tiledGridID, std::move(tiledGridParams)); - drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(tiledGridManager, intendedNextSubmit); + drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(tiledGridID, tiledGridManager.georeferencedImageParams, intendedNextSubmit); drawResourcesFiller.addGeoreferencedImage(tiledGridManager, inverseViewProj, intendedNextSubmit); } From 6a1b76ed0b7ed810dba7dd248c2c0821e91c6b4a Mon Sep 17 00:00:00 2001 From: Fletterio Date: Wed, 13 Aug 2025 16:32:06 -0300 Subject: [PATCH 11/11] Some names are wrong here, but the example still works --- 62_CAD/DrawResourcesFiller.cpp | 134 ++++++++++++++++++++------------- 62_CAD/DrawResourcesFiller.h | 91 +++++++++++++++++++++- 62_CAD/Images.h | 5 +- 62_CAD/main.cpp | 60 ++++++++++----- 4 files changed, 208 insertions(+), 82 deletions(-) diff --git a/62_CAD/DrawResourcesFiller.cpp b/62_CAD/DrawResourcesFiller.cpp index 547feca64..5d2fea009 100644 --- a/62_CAD/DrawResourcesFiller.cpp +++ b/62_CAD/DrawResourcesFiller.cpp @@ -2679,8 +2679,8 @@ void DrawResourcesFiller::flushDrawObjects() } } -DrawResourcesFiller::StreamedImageManager::StreamedImageManager(image_id _imageID, GeoreferencedImageParams&& _georeferencedImageParams) - : imageID(_imageID), georeferencedImageParams(std::move(_georeferencedImageParams)) +DrawResourcesFiller::StreamedImageManager::StreamedImageManager(image_id _imageID, GeoreferencedImageParams&& _georeferencedImageParams, ImageLoader&& _loader) + : imageID(_imageID), georeferencedImageParams(std::move(_georeferencedImageParams)), loader(std::move(_loader)) { maxImageTileIndices = georeferencedImageParams.imageExtents / uint32_t2(TileSize, TileSize); // If it fits perfectly along any dimension, we need one less tile with this scheme @@ -2709,14 +2709,8 @@ DrawResourcesFiller::StreamedImageManager::StreamedImageManager(image_id _imageI float64_t2 secondRow = dirV / dirVLengthSquared; float64_t2x2 changeOfBasisMatrix(firstRow, secondRow); - // 3. Scaling. The vector obtained by doing `CoB * displacement * p` are now the coordinates in the `span{dirU, dirV}`, which would be `uv` coordinates in [0,1]^2 - // (or outside this range for points not in the image). To get tile lattice coordinates, we need to scale this number by an nTiles vector which counts - // (fractionally) how many tiles fit in the image along each axis - float32_t2 nTiles = float32_t2(georeferencedImageParams.imageExtents) / float32_t2(TileSize, TileSize); - float64_t2x2 scaleMatrix(nTiles.x, 0., 0., nTiles.y); - // Put them all together - world2Tile = nbl::hlsl::mul(scaleMatrix, nbl::hlsl::mul(changeOfBasisMatrix, displacementMatrix)); + world2UV = nbl::hlsl::mul(changeOfBasisMatrix, displacementMatrix); } DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::StreamedImageManager::generateTileUploadData(const ImageType imageType, const float64_t3x3& NDCToWorld) @@ -2725,55 +2719,38 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S if (imageType == ImageType::GEOREFERENCED_FULL_RESOLUTION) //Pass imageID as parameter, down from the addGeoRef call return TileUploadData{ {}, georeferencedImageParams.worldspaceOBB }; - // Following need only be done if image is actually streamed - - // Using Vulkan NDC, the viewport has coordinates in the range [-1, -1] x [1,1]. First we get the world coordinates of the viewport corners, in homogenous - const float64_t3 topLeftNDCH(-1., -1., 1.); - const float64_t3 topRightNDCH(1., -1., 1.); - const float64_t3 bottomLeftNDCH(-1., 1., 1.); - const float64_t3 bottomRightNDCH(1., 1., 1.); - - const float64_t3 topLeftWorldH = nbl::hlsl::mul(NDCToWorld, topLeftNDCH); - const float64_t3 topRightWorldH = nbl::hlsl::mul(NDCToWorld, topRightNDCH); - const float64_t3 bottomLeftWorldH = nbl::hlsl::mul(NDCToWorld, bottomLeftNDCH); - const float64_t3 bottomRightWorldH = nbl::hlsl::mul(NDCToWorld, bottomRightNDCH); - - // We can use `world2Tile` to get tile lattice coordinates for each of these points - const float64_t2 topLeftTileLattice = nbl::hlsl::mul(world2Tile, topLeftWorldH); - const float64_t2 topRightTileLattice = nbl::hlsl::mul(world2Tile, topRightWorldH); - const float64_t2 bottomLeftTileLattice = nbl::hlsl::mul(world2Tile, bottomLeftWorldH); - const float64_t2 bottomRightTileLattice = nbl::hlsl::mul(world2Tile, bottomRightWorldH); - - // Get the min and max of each lattice coordinate - const float64_t2 minTop = nbl::hlsl::min(topLeftTileLattice, topRightTileLattice); - const float64_t2 minBottom = nbl::hlsl::min(bottomLeftTileLattice, bottomRightTileLattice); - const float64_t2 minAll = nbl::hlsl::min(minTop, minBottom); - - const float64_t2 maxTop = nbl::hlsl::max(topLeftTileLattice, topRightTileLattice); - const float64_t2 maxBottom = nbl::hlsl::max(bottomLeftTileLattice, bottomRightTileLattice); - const float64_t2 maxAll = nbl::hlsl::max(maxTop, maxBottom); - - // Floor them to get an integer for the tiles they're in - const int32_t2 minAllFloored = nbl::hlsl::floor(minAll); - const int32_t2 maxAllFloored = nbl::hlsl::floor(maxAll); - - // Clamp them to reasonable tile indices - minLoadedTileIndices = nbl::hlsl::clamp(minAllFloored, int32_t2(0, 0), int32_t2(maxImageTileIndices)); - maxLoadedTileIndices = nbl::hlsl::clamp(maxAllFloored, int32_t2(0, 0), nbl::hlsl::min(int32_t2(maxImageTileIndices), int32_t2(minLoadedTileIndices + maxResidentTiles - uint32_t2(1,1)))); + currentMappedRegion = computeViewportTileAlignedObb(NDCToWorld); // Now we have the indices of the tiles we want to upload, so create the vector of `StreamedImageCopies` - 1 per tile. core::vector tiles; - tiles.reserve((maxLoadedTileIndices.x - minLoadedTileIndices.x + 1) * (maxLoadedTileIndices.y - minLoadedTileIndices.y + 1)); + uint32_t nTiles = (currentMappedRegion.bottomRight.x - currentMappedRegion.topLeft.x + 1) * (currentMappedRegion.topLeft.y - currentMappedRegion.topLeft.y + 1); + tiles.reserve(nTiles); // Assuming a 1 pixel per block format - otherwise math here gets a bit trickier auto bytesPerPixel = getTexelOrBlockBytesize(georeferencedImageParams.format); const size_t bytesPerSide = bytesPerPixel * TileSize; // Dangerous code - assumes image can be perfectly covered with tiles. Otherwise will need to handle edge cases - for (uint32_t tileX = minLoadedTileIndices.x; tileX <= maxLoadedTileIndices.x; tileX++) + for (uint32_t tileX = currentMappedRegion.topLeft.x; tileX <= currentMappedRegion.bottomRight.x; tileX++) { - for (uint32_t tileY = minLoadedTileIndices.y; tileY <= maxLoadedTileIndices.y; tileY++) + for (uint32_t tileY = currentMappedRegion.topLeft.y; tileY <= currentMappedRegion.bottomRight.y; tileY++) { + // Flush the loaded sections into the buffer - they should be done uploading by now + loader.clear(); + // Reserve enough sections + loader.reserve(nTiles); + auto tile = loader.load(uint32_t2(tileX * TileSize, tileY * TileSize), uint32_t2(TileSize, TileSize)); + // Alias the buffer + asset::IBuffer::SCreationParams bufParams = { .size = tile->getBuffer()->getSize(), .usage = tile->getBuffer()->getUsageFlags() }; + ICPUBuffer::SCreationParams cpuBufParams(std::move(bufParams)); + cpuBufParams.data = tile->getBuffer()->getPointer(); + cpuBufParams.memoryResource = core::getNullMemoryResource(); + auto aliasedBuffer = ICPUBuffer::create(std::move(cpuBufParams), nbl::core::adopt_memory_t{}); + + // The math here is like this because of the buffer we're getting (full image in the emulated case) + // When moving to actual ECW loading, bufferOffset will be 0, bufferRowLength will be the extent.width of the loaded section, + // imageExtent will be the extent of the loaded section, and imageOffset will be the appropriate offset (we will be loading whole sections of tiles + // that can be made into a rectangle instead of tile by tile) asset::IImage::SBufferCopy bufCopy; bufCopy.bufferOffset = (tileY * (maxImageTileIndices.x + 1) * TileSize + tileX) * bytesPerSide; bufCopy.bufferRowLength = georeferencedImageParams.imageExtents.x; @@ -2782,12 +2759,12 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S bufCopy.imageSubresource.mipLevel = 0u; bufCopy.imageSubresource.baseArrayLayer = 0u; bufCopy.imageSubresource.layerCount = 1u; - bufCopy.imageOffset = { (tileX - minLoadedTileIndices.x) * TileSize, (tileY - minLoadedTileIndices.y) * TileSize, 0u }; + bufCopy.imageOffset = { (tileX - currentMappedRegion.topLeft.x) * TileSize, (tileY - currentMappedRegion.topLeft.y) * TileSize, 0u }; bufCopy.imageExtent.width = TileSize; bufCopy.imageExtent.height = TileSize; bufCopy.imageExtent.depth = 1; - tiles.emplace_back(georeferencedImageParams.format, georeferencedImageParams.geoReferencedImage->getBuffer(), std::move(bufCopy)); + tiles.emplace_back(georeferencedImageParams.format, aliasedBuffer, std::move(bufCopy)); } } @@ -2796,8 +2773,8 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S OrientedBoundingBox2D worldspaceOBB = fromTopLeftOBB; const float32_t2 dirV = float32_t2(worldspaceOBB.dirU.y, -worldspaceOBB.dirU.x) * worldspaceOBB.aspectRatio; - worldspaceOBB.topLeft += worldspaceOBB.dirU * float32_t(minLoadedTileIndices.x) / float32_t(maxResidentTiles.x); - worldspaceOBB.topLeft += dirV * float32_t(minLoadedTileIndices.y) / float32_t(maxResidentTiles.y); + worldspaceOBB.topLeft += worldspaceOBB.dirU * float32_t(currentMappedRegion.topLeft.x) / float32_t(maxResidentTiles.x); + worldspaceOBB.topLeft += dirV * float32_t(currentMappedRegion.topLeft.y) / float32_t(maxResidentTiles.y); // Compute minUV, maxUV // Since right now we don't shift the obb around, minUV will always be (0,0), but this is bound to change later on (shifting obb will happen when we want to reuse tiles and not @@ -2806,14 +2783,14 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S // By default we have a large-as-possible obb so maxUV is (1,1). This is bound to change in the general case when we shift obb. However, since right now we need to shrink the obb // to ensure we don't draw outside the bounds of the real image, we also need to change the maxUV; float32_t2 maxUV(1.f, 1.f); - int32_t excessTiles = minLoadedTileIndices.x + maxResidentTiles.x - 1 - maxImageTileIndices.x; + int32_t excessTiles = currentMappedRegion.topLeft.x + maxResidentTiles.x - 1 - maxImageTileIndices.x; if (excessTiles > 0) { // Shrink obb to only fit necessary tiles, compute maxUV.x which turns out to be exactly the shrink factor for dirU. maxUV.x = float32_t(maxResidentTiles.x - excessTiles) / maxResidentTiles.x; } // De the same along the other axis - excessTiles = minLoadedTileIndices.y + maxResidentTiles.y - 1 - maxImageTileIndices.y; + excessTiles = currentMappedRegion.topLeft.y + maxResidentTiles.y - 1 - maxImageTileIndices.y; if (excessTiles > 0) { // Analogously, maxUV.y is the shrink factor for dirV. @@ -2827,4 +2804,53 @@ DrawResourcesFiller::StreamedImageManager::TileUploadData DrawResourcesFiller::S return TileUploadData{ std::move(tiles), worldspaceOBB, minUV, maxUV }; -} \ No newline at end of file +} + +float64_t DrawResourcesFiller::StreamedImageManager::computeViewportMipLevel(const float64_t3x3& NDCToWorld, float64_t2 viewportExtents) +{ + const auto viewportWidthVectorWorld = nbl::hlsl::mul(NDCToWorld, topRightViewportNDCH - topLeftViewportNDCH); + const auto viewportHeightVectorWorld = nbl::hlsl::mul(NDCToWorld, bottomLeftViewportNDCH - topLeftViewportNDCH); + // Abuse of notation, we're passing a vector not a point, won't be affected by offset so that's ok + const auto viewportWidthImagePixelLengthVector = transformWorldCoordsToPixelCoords(viewportWidthVectorWorld); + const auto viewportHeightImagePixelLengthVector = transformWorldCoordsToPixelCoords(viewportHeightVectorWorld); + + const auto viewportWidthImagePixelLength = nbl::hlsl::length(viewportWidthImagePixelLengthVector); + const auto viewportHeightImagePixelLength = nbl::hlsl::length(viewportHeightImagePixelLengthVector); + + // Mip is decided based on max of these + const auto maxPixelLength = nbl::hlsl::max(viewportWidthImagePixelLength, viewportHeightImagePixelLength); + return maxPixelLength / nbl::hlsl::max(viewportExtents.x, viewportExtents.y); +} + +DrawResourcesFiller::StreamedImageManager::TileLatticeAlignedObb DrawResourcesFiller::StreamedImageManager::computeViewportTileAlignedObb(const float64_t3x3& NDCToWorld) +{ + const float64_t3 topLeftViewportWorldH = nbl::hlsl::mul(NDCToWorld, topLeftViewportNDCH); + const float64_t3 topRightViewportWorldH = nbl::hlsl::mul(NDCToWorld, topRightViewportNDCH); + const float64_t3 bottomLeftViewportWorldH = nbl::hlsl::mul(NDCToWorld, bottomLeftViewportNDCH); + const float64_t3 bottomRightViewportWorldH = nbl::hlsl::mul(NDCToWorld, bottomRightViewportNDCH); + + // We can use `world2Tile` to get tile lattice coordinates for each of these points + const float64_t2 topLeftTileLattice = transformWorldCoordsToTileCoords(topLeftViewportWorldH); + const float64_t2 topRightTileLattice = transformWorldCoordsToTileCoords(topRightViewportWorldH); + const float64_t2 bottomLeftTileLattice = transformWorldCoordsToTileCoords(bottomLeftViewportWorldH); + const float64_t2 bottomRightTileLattice = transformWorldCoordsToTileCoords(bottomRightViewportWorldH); + + // Get the min and max of each lattice coordinate + const float64_t2 minTop = nbl::hlsl::min(topLeftTileLattice, topRightTileLattice); + const float64_t2 minBottom = nbl::hlsl::min(bottomLeftTileLattice, bottomRightTileLattice); + const float64_t2 minAll = nbl::hlsl::min(minTop, minBottom); + + const float64_t2 maxTop = nbl::hlsl::max(topLeftTileLattice, topRightTileLattice); + const float64_t2 maxBottom = nbl::hlsl::max(bottomLeftTileLattice, bottomRightTileLattice); + const float64_t2 maxAll = nbl::hlsl::max(maxTop, maxBottom); + + // Floor them to get an integer for the tiles they're in + const int32_t2 minAllFloored = nbl::hlsl::floor(minAll); + const int32_t2 maxAllFloored = nbl::hlsl::floor(maxAll); + + // Clamp them to reasonable tile indices + TileLatticeAlignedObb retVal = {}; + retVal.topLeft = nbl::hlsl::clamp(minAllFloored, int32_t2(0, 0), int32_t2(maxImageTileIndices)); + retVal.bottomRight = nbl::hlsl::clamp(maxAllFloored, int32_t2(0, 0), nbl::hlsl::min(int32_t2(maxImageTileIndices), int32_t2(currentMappedRegion.topLeft + maxResidentTiles - uint32_t2(1, 1)))); + return retVal; +} diff --git a/62_CAD/DrawResourcesFiller.h b/62_CAD/DrawResourcesFiller.h index 6d29e9637..3855afba6 100644 --- a/62_CAD/DrawResourcesFiller.h +++ b/62_CAD/DrawResourcesFiller.h @@ -123,6 +123,66 @@ struct DrawResourcesFiller } }; + // Used to load pieces of an ECW from disk - currently just emulated + struct ImageLoader + { + ImageLoader(core::smart_refctd_ptr&& _geoReferencedImage) : geoReferencedImage(std::move(_geoReferencedImage)) {} + + // Emulates the loading of a rectangle from the original image + core::smart_refctd_ptr load(uint32_t2 offset, uint32_t2 extent) + { + // Create a new buffer pointing to same data - this is not what the class will be doing when streaming ECW + auto imageBuffer = geoReferencedImage->getBuffer(); + asset::IBuffer::SCreationParams bufCreationParams = { .size = imageBuffer->getSize(), .usage = imageBuffer->getUsageFlags() }; + ICPUBuffer::SCreationParams cpuBufCreationParams(std::move(bufCreationParams)); + cpuBufCreationParams.data = imageBuffer->getPointer(); + cpuBufCreationParams.memoryResource = core::getNullMemoryResource(); + auto imageBufferAlias = ICPUBuffer::create(std::move(cpuBufCreationParams), core::adopt_memory_t{}); + // Now set up the image region + auto bytesPerPixel = getTexelOrBlockBytesize(geoReferencedImage->getCreationParameters().format); + + auto regions = core::make_refctd_dynamic_array>(1u); + auto& region = regions->front(); + region.bufferOffset = (offset.y * geoReferencedImage->getCreationParameters().extent.width + offset.x) * bytesPerPixel; + region.bufferRowLength = geoReferencedImage->getCreationParameters().extent.width; + region.bufferImageHeight = 0; + region.imageSubresource.aspectMask = IImage::EAF_COLOR_BIT; + region.imageSubresource.mipLevel = 0u; + region.imageSubresource.baseArrayLayer = 0u; + region.imageSubresource.layerCount = 1u; + region.imageOffset = { 0u, 0u, 0u }; + region.imageExtent.width = extent.x; + region.imageExtent.height = extent.y; + region.imageExtent.depth = 1; + + ICPUImage::SCreationParams loadedImageParams = geoReferencedImage->getCreationParameters(); + loadedImageParams.extent = { extent.x, extent.y, 1u }; + auto loadedImage = ICPUImage::create(std::move(loadedImageParams)); + loadedImage->setBufferAndRegions(std::move(imageBufferAlias), regions); + + loadedSections.push_back(loadedImage); + + return loadedImage; + } + + void clear() + { + loadedSections.clear(); + } + + void reserve(uint32_t sections) + { + loadedSections.reserve(sections); + } + + // This will be the path to the image + core::smart_refctd_ptr geoReferencedImage; + private: + // This will be actually loaded sections (each section is a rectangle of one or more tiles) of the above image. Since we alias buffers for uploads, + // we want this class to track their lifetime so they don't get deallocated before they get uploaded. + core::vector> loadedSections; + }; + // @brief Used to load tiles into VRAM, keep track of loaded tiles, determine how they get sampled etc. struct StreamedImageManager { @@ -130,7 +190,18 @@ struct DrawResourcesFiller constexpr static uint32_t TileSize = 128u; constexpr static uint32_t PaddingTiles = 2; - StreamedImageManager(image_id _imageID, GeoreferencedImageParams&& _georeferencedImageParams); + constexpr static float64_t3 topLeftViewportNDCH = float64_t3(-1.0, -1.0, 1.0); + constexpr static float64_t3 topRightViewportNDCH = float64_t3(1.0, -1.0, 1.0); + constexpr static float64_t3 bottomLeftViewportNDCH = float64_t3(-1.0, 1.0, 1.0); + constexpr static float64_t3 bottomRightViewportNDCH = float64_t3(1.0, 1.0, 1.0); + + StreamedImageManager(image_id _imageID, GeoreferencedImageParams&& _georeferencedImageParams, ImageLoader&& _loader); + + struct TileLatticeAlignedObb + { + uint32_t2 topLeft; + uint32_t2 bottomRight; + }; struct TileUploadData { @@ -140,20 +211,32 @@ struct DrawResourcesFiller float32_t2 maxUV; }; + // Right now it's generating tile-by-tile. Can be improved to produce at worst 4 different rectangles to load (depending on how we need to load tiles) TileUploadData generateTileUploadData(const ImageType imageType, const float64_t3x3& NDCToWorld); + // These are NOT UV, pixel or tile coords into the mapped image region, rather into the real, huge image + // Tile coords are always in mip 0 tile size. Translating to other mips levels is trivial + float64_t2 transformWorldCoordsToUV(const float64_t3 worldCoordsH) {return nbl::hlsl::mul(world2UV, worldCoordsH);} + float64_t2 transformWorldCoordsToPixelCoords(const float64_t3 worldCoordsH) { return float64_t2(georeferencedImageParams.imageExtents) * transformWorldCoordsToUV(worldCoordsH); } + float64_t2 transformWorldCoordsToTileCoords(const float64_t3 worldCoordsH) { return (1.0 / TileSize) * transformWorldCoordsToPixelCoords(worldCoordsH); } + + // Compute repeated here, can both be done together if necessary + float64_t computeViewportMipLevel(const float64_t3x3& NDCToWorld, float64_t2 viewportExtents); + TileLatticeAlignedObb computeViewportTileAlignedObb(const float64_t3x3& NDCToWorld); + image_id imageID; GeoreferencedImageParams georeferencedImageParams; // This and the logic they're in will likely change later with Toroidal updating private: + uint32_t currentResidentMipLevel = {}; uint32_t2 maxResidentTiles = {}; - uint32_t2 minLoadedTileIndices = {}; - uint32_t2 maxLoadedTileIndices = {}; uint32_t2 maxImageTileIndices = {}; - float64_t2x3 world2Tile = {}; + TileLatticeAlignedObb currentMappedRegion = {}; + float64_t2x3 world2UV = {}; // Worldspace OBB that covers the top left `maxResidentTiles.x x maxResidentTiles.y` tiles of the image. // We shift this OBB by appropriate tile offsets when loading tiles OrientedBoundingBox2D fromTopLeftOBB = {}; + ImageLoader loader; }; DrawResourcesFiller(); diff --git a/62_CAD/Images.h b/62_CAD/Images.h index 5329a5bda..2e66f3c44 100644 --- a/62_CAD/Images.h +++ b/62_CAD/Images.h @@ -28,9 +28,6 @@ struct GeoreferencedImageParams uint32_t2 imageExtents = {}; uint32_t2 viewportExtents = {}; asset::E_FORMAT format = {}; - // For now it's going to be fully resident in memory, later on it's probably going to be a streamer class most likely. - core::smart_refctd_ptr geoReferencedImage; - // TODO: Need to add other stuff later. }; /** @@ -207,7 +204,7 @@ class ImagesCache : public core::ResizableLRUCache struct StreamedImageCopy { asset::E_FORMAT srcFormat; - ICPUBuffer* srcBuffer; // Make it 'std::future' later? + smart_refctd_ptr srcBuffer; // Make it 'std::future' later? asset::IImage::SBufferCopy region; }; diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index e420a81af..62496ef64 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -49,6 +49,7 @@ static constexpr bool DebugRotatingViewProj = false; static constexpr bool FragmentShaderPixelInterlock = true; static constexpr bool LargeGeoTextureStreaming = true; static constexpr bool CacheAndReplay = false; // caches first frame resources (buffers and images) from DrawResourcesFiller and replays in future frames, skiping CPU Logic +static constexpr bool textCameraRotation = true; enum class ExampleMode { @@ -1497,17 +1498,9 @@ class ComputerAidedDesign final : public nbl::examples::SimpleWindowedApplicatio projectionToNDC = m_Camera.constructViewProjection(); // TEST CAMERA ROTATION -#if 0 - // double rotation = 0.25 * PI(); - double rotation = abs(cos(m_timeElapsed * 0.0004)) * 0.25 * PI() ; - float64_t2 rotationVec = float64_t2(cos(rotation), sin(rotation)); - float64_t3x3 rotationParameter = float64_t3x3 { - rotationVec.x, rotationVec.y, 0.0, - -rotationVec.y, rotationVec.x, 0.0, - 0.0, 0.0, 1.0 - }; - projectionToNDC = nbl::hlsl::mul(projectionToNDC, rotationParameter); -#endif + if constexpr (textCameraRotation) + projectionToNDC = rotateBasedOnTime(projectionToNDC); + Globals globalData = {}; uint64_t baseAddress = resourcesGPUBuffer->getDeviceAddress(); globalData.pointers = { @@ -3687,30 +3680,45 @@ class ComputerAidedDesign final : public nbl::examples::SimpleWindowedApplicatio } else if (mode == ExampleMode::CASE_12) { + const static float64_t3 topLeftViewportH = float64_t3(-1.0, -1.0, 1.0); + const static float64_t3 topRightViewportH = float64_t3(1.0, -1.0, 1.0); + const static float64_t3 bottomLeftViewportH = float64_t3(-1.0, 1.0, 1.0); + const static float64_t3 bottomRightViewportH = float64_t3(1.0, 1.0, 1.0); + image_id tiledGridID = 6996; - GeoreferencedImageParams tiledGridParams; + static GeoreferencedImageParams tiledGridParams; auto& tiledGridCreationParams = bigTiledGrid->getCreationParameters(); // Position at topLeft viewport - auto inverseViewProj = nbl::hlsl::inverse(m_Camera.constructViewProjection()); - const float64_t3 topLeftViewportH = float64_t3(-1.0, -1.0, 1.0); + auto projectionToNDC = m_Camera.constructViewProjection(); + // TEST CAMERA ROTATION + if constexpr (textCameraRotation) + projectionToNDC = rotateBasedOnTime(projectionToNDC); + auto inverseViewProj = nbl::hlsl::inverse(projectionToNDC); + const static auto startingTopLeft = nbl::hlsl::mul(inverseViewProj, topLeftViewportH); tiledGridParams.worldspaceOBB.topLeft = startingTopLeft; - // Get screen pixel to match 2 viewport pixels (to test at mip border) by choosing appropriate dirU - const float64_t3 topRightViewportH = float64_t3(1.0, -1.0, 1.0); - const static auto startingViewportLengthVector = nbl::hlsl::mul(inverseViewProj, topRightViewportH - topLeftViewportH); - const static auto dirU = startingViewportLengthVector * float64_t(bigTiledGrid->getCreationParameters().extent.width) / float64_t(2 * m_window->getWidth()); + + // Get 1 viewport pixel to match `startingImagePixelsPerViewportPixel` pixels of the image by choosing appropriate dirU + const static float64_t startingImagePixelsPerViewportPixels = 2.0; + const static auto startingViewportWidthVector = nbl::hlsl::mul(inverseViewProj, topRightViewportH - topLeftViewportH); + const static auto dirU = startingViewportWidthVector * float64_t(bigTiledGrid->getCreationParameters().extent.width) / float64_t(startingImagePixelsPerViewportPixels * m_window->getWidth()); tiledGridParams.worldspaceOBB.dirU = dirU; tiledGridParams.worldspaceOBB.aspectRatio = 1.0; tiledGridParams.imageExtents = { tiledGridCreationParams.extent.width, tiledGridCreationParams.extent.height}; tiledGridParams.viewportExtents = uint32_t2{ m_window->getWidth(), m_window->getHeight() }; tiledGridParams.format = tiledGridCreationParams.format; - tiledGridParams.geoReferencedImage = bigTiledGrid; - DrawResourcesFiller::StreamedImageManager tiledGridManager(tiledGridID, std::move(tiledGridParams)); + static auto bigTileGridPtr = bigTiledGrid; + static DrawResourcesFiller::ImageLoader loader(std::move(bigTileGridPtr)); + static DrawResourcesFiller::StreamedImageManager tiledGridManager(tiledGridID, std::move(tiledGridParams), std::move(loader)); drawResourcesFiller.ensureGeoreferencedImageAvailability_AllocateIfNeeded(tiledGridID, tiledGridManager.georeferencedImageParams, intendedNextSubmit); drawResourcesFiller.addGeoreferencedImage(tiledGridManager, inverseViewProj, intendedNextSubmit); + + // Mip level calculation + // Uncomment to print mip + //std::cout << "mip level: " << tiledGridManager.computeViewportMipLevel(inverseViewProj, float64_t2(m_window->getWidth(), m_window->getHeight())) << std::endl; } } @@ -3721,6 +3729,18 @@ class ComputerAidedDesign final : public nbl::examples::SimpleWindowedApplicatio return hlsl::length(float64_t2(idx_0_0, idx_1_0)); } + float64_t3x3 rotateBasedOnTime(const float64_t3x3& projectionMatrix) + { + double rotation = abs(cos(m_timeElapsed * 0.0004)) * 0.25 * PI(); + float64_t2 rotationVec = float64_t2(cos(rotation), sin(rotation)); + float64_t3x3 rotationParameter = float64_t3x3{ + rotationVec.x, rotationVec.y, 0.0, + -rotationVec.y, rotationVec.x, 0.0, + 0.0, 0.0, 1.0 + }; + return nbl::hlsl::mul(projectionMatrix, rotationParameter); + } + protected: std::chrono::seconds timeout = std::chrono::seconds(0x7fffFFFFu); clock_t::time_point start;