From 6a74ab15c39406bcf0a0ae304104ddee47b8c2ac Mon Sep 17 00:00:00 2001 From: Fletterio Date: Wed, 23 Jul 2025 20:38:14 -0300 Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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);