/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ #pragma once #include "kernel/globals.h" #include "kernel/sample/lcg.h" #include "util/atomic.h" #include "util/defines.h" #include "util/math_fast.h" #include "util/types_image.h" CCL_NAMESPACE_BEGIN ccl_device_forceinline int kernel_image_udim_map(KernelGlobals kg, const int id, ccl_private float2 &uv) { if (id >= 0) { return id; } const int tx = (int)uv.x; const int ty = (int)uv.y; if (tx < 0 || ty < 0 || tx >= 10) { return KERNEL_IMAGE_NONE; } int udim_id = -id - 1; const int num_udims = kernel_data_fetch(image_texture_udims, udim_id++).tile; const int tile = 1001 + 10 * ty + tx; for (int i = 0; i < num_udims; i++) { const KernelImageUDIM udim = kernel_data_fetch(image_texture_udims, udim_id++); if (udim.tile == tile) { /* If we found the tile, offset the UVs to be relative to it. */ uv.x -= tx; uv.y -= ty; return udim.image_texture_id; } } return KERNEL_IMAGE_NONE; } ccl_device_forceinline bool kernel_image_tile_wrap(const ExtensionType extension, ccl_private float2 &uv) { /* Wrapping. */ switch (extension) { case EXTENSION_REPEAT: uv = uv - floor(uv); return true; case EXTENSION_CLIP: return (uv.x >= 0.0f && uv.x <= 1.0f && uv.y >= 0.0f && uv.y <= 1.0f); case EXTENSION_EXTEND: uv = clamp(uv, zero_float2(), one_float2()); return true; case EXTENSION_MIRROR: { const float2 t = uv * 0.5f; uv = 2.0f * (t - floor(t)); uv = select(uv >= one_float2(), 2.0f * one_float2() - uv, uv); return true; } default: break; } return false; } /* From UV coordinates in 0..1 range, compute tile and pixel coordinates. */ ccl_device_forceinline KernelTileDescriptor kernel_image_tile_map(KernelGlobals kg, ccl_private ShaderData *sd, const ccl_global KernelImageTexture &tex, const uint image_texture_id, const dual2 uv, ccl_private float2 &xy) { /* Find mipmap level. Use squared lengths to avoid two sqrt operations, * compensating with 0.5 factor on the log2. Square as float to avoid * integer overflow. */ const float dudxy_sq = len_squared(make_float2(uv.dx.x, uv.dy.x)) * sqr(float(tex.width)); const float dvdxy_sq = len_squared(make_float2(uv.dx.y, uv.dy.y)) * sqr(float(tex.height)); /* Limit max anisotropy ratio, to avoid loading too high mip resolutions * for stretched UV coordinates, which don't really benefit from it anyway. */ const float maxdxy_sq = max(dudxy_sq, dvdxy_sq); const float mindxy_sq = min(dudxy_sq, dvdxy_sq); const float inv_aniso_ratio_sq = 1.0f / (16.0f * 16.0f); /* Native log2 is faster on GPU. */ #ifdef __KERNEL_GPU__ float flevel = 0.5f * log2(max(mindxy_sq, maxdxy_sq * inv_aniso_ratio_sq)); #else float flevel = 0.5f * fast_log2f(max(mindxy_sq, maxdxy_sq * inv_aniso_ratio_sq)); #endif /* Select mipmap level. */ if (sd->lcg_state != 0) { /* For rounding instead of flooring. */ flevel += 0.5f; /* Randomize mip level, except for some cases like displacement or importance map. */ const float transition = 0.5f; flevel += (lcg_step_float(&sd->lcg_state) - 0.5f) * transition; } else { /* When not using stochastic interpolation, round to higher level. */ } flevel += kernel_data.image.mip_bias; const int level = clamp(int(flevel), 0, tex.tile_levels - 1); /* Compute width of this mipmap level. */ const int width = max(1, tex.width >> level); const int height = max(1, tex.height >> level); /* Convert coordinates to pixel space. * Flip Y convention for tiles to match tx files. */ xy = make_float2(uv.val.x * width, (1.0f - uv.val.y) * height); /* Tile mapping */ const int ix = clamp((int)xy.x, 0, width - 1); const int iy = clamp((int)xy.y, 0, height - 1); const int tile_size_shift = tex.tile_size_shift; const int tile_size_padded = (1 << tile_size_shift) + KERNEL_IMAGE_TEX_PADDING * 2; const int tile_x = ix >> tile_size_shift; const int tile_y = iy >> tile_size_shift; const int tile_offset = kernel_data_fetch(image_texture_tile_descriptors, tex.tile_descriptor_offset + level) + tile_x + tile_y * divide_up_by_shift(width, tile_size_shift); KernelTileDescriptor tile_descriptor = kernel_data_fetch( image_texture_tile_descriptors, tex.tile_descriptor_offset + tile_offset); const uint access_index = tex.tile_descriptor_offset + tile_offset; if (!kernel_tile_descriptor_loaded(tile_descriptor)) { #ifdef __KERNEL_GPU__ /* For GPU, mark load requested and cancel shader execution. */ if (tile_descriptor == KERNEL_TILE_LOAD_NONE) { tile_descriptor = KERNEL_TILE_LOAD_REQUEST; /* Write load request for quick rejection of subsequent reads. */ kernel_data_write(image_texture_tile_descriptors, tex.tile_descriptor_offset + tile_offset, tile_descriptor); /* Set access state that will be read back to host. Using a byte * instead of a bitmask avoids the need for atomics. */ kernel_data_array( image_texture_tile_access_state)[access_index] = KERNEL_TILE_ACCESS_REQUESTED; } if (tile_descriptor == KERNEL_TILE_LOAD_REQUEST) { sd->runtime_flag |= SR_CACHE_MISS; } return tile_descriptor; #else /* For CPU, load tile immediately. */ if (tile_descriptor != KERNEL_TILE_LOAD_FAILED) { KernelTileDescriptor &p_tile_descriptor = kg->image_texture_tile_descriptors.data[access_index]; kg->image_load_requested_cpu(image_texture_id, level, tile_x << tile_size_shift, tile_y << tile_size_shift, p_tile_descriptor); tile_descriptor = p_tile_descriptor; } if (!kernel_tile_descriptor_loaded(tile_descriptor)) { return tile_descriptor; } #endif } /* Mark tile as used for cache eviction tracking. Read before writing as we * expect most of the time this was already written. */ if (kernel_data_array(image_texture_tile_access_state)[access_index] != KERNEL_TILE_ACCESS_USED) { kernel_data_array(image_texture_tile_access_state)[access_index] = KERNEL_TILE_ACCESS_USED; } /* Remap coordinates into tiled image space. */ const int offset = kernel_tile_descriptor_offset(tile_descriptor); xy += make_float2(KERNEL_IMAGE_TEX_PADDING - (tile_x << tile_size_shift) + offset * tile_size_padded, KERNEL_IMAGE_TEX_PADDING - (tile_y << tile_size_shift)); return tile_descriptor; } CCL_NAMESPACE_END