textureAPI.h 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. 
  2. // zlib open source license
  3. //
  4. // Copyright (c) 2025 David Forsgren Piuva
  5. //
  6. // This software is provided 'as-is', without any express or implied
  7. // warranty. In no event will the authors be held liable for any damages
  8. // arising from the use of this software.
  9. //
  10. // Permission is granted to anyone to use this software for any purpose,
  11. // including commercial applications, and to alter it and redistribute it
  12. // freely, subject to the following restrictions:
  13. //
  14. // 1. The origin of this software must not be misrepresented; you must not
  15. // claim that you wrote the original software. If you use this software
  16. // in a product, an acknowledgment in the product documentation would be
  17. // appreciated but is not required.
  18. //
  19. // 2. Altered source versions must be plainly marked as such, and must not be
  20. // misrepresented as being the original software.
  21. //
  22. // 3. This notice may not be removed or altered from any source
  23. // distribution.
  24. // Everything stored directly in the image types is immutable to allow value types to behave like reference types using the data that they point to.
  25. // Image types can not be dynamically casted, because the inheritance is entirely static without any virtual functions.
  26. // TODO: Create a fast way to generate masks from an exponential scale floating mip level taken from sampling distances.
  27. // float samplingDistance (input expressed as some kind of distance in the uv coordinates between two adjacent pixels)
  28. // uint32_t tileXYMask (tiling should be applied to X and Y using the same mask after limiting to 16 bit integers)
  29. // uint32_t maxLevelMask
  30. // So how do we get the weights without shifting bits by the actual bit offset?
  31. // Maybe add one to the mask to get a single bit and then multiply.
  32. /*
  33. TODO: Try to handle negative texture coordinates and let positive UV be an optimization flag to enable when known to be valid.
  34. Convert to int32_t with less range and convert to unsigned correctly in modulo of 24 bits.
  35. // Use leading zeroes to create a mask, which can be turned into a power of two by adding one.
  36. // 0001000000000000 -> 0001111111111111
  37. // 0001011001000100 -> 0001111111111111
  38. // 0001111111111111 -> 0001111111111111
  39. // 0000010000000000 -> 0000011111111111
  40. // 0000010110010001 -> 0000011111111111
  41. // 0000011111111111 -> 0000011111111111
  42. // 0000000000100000 -> 0000000000111111
  43. // 0000000000101100 -> 0000000000111111
  44. // 0000000000111111 -> 0000000000111111
  45. uint16_t maskFromLeadingZeroes(uint16_t value) {
  46. // Turning 10 into 11
  47. uint16_t result = value | (value >> 1);
  48. // Turning 1100 into 1111
  49. result = result | (result >> 2);
  50. // Turning 11110000 into 11111111
  51. result = result | (result >> 4);
  52. // Turning 1111111100000000 into 1111111111111111
  53. result = result | (result >> 8);
  54. }
  55. Generate masks for sampling a specific texture at a specific mip level.
  56. They can then be reused for multiple samples.
  57. Pre-condition:
  58. 0.0f < samplingDistance
  59. Use min, max, absm et cetera to create a positive sampling distance.
  60. void createMasks(float samplingDistance) {
  61. uint32_t density = truncateToU32(reciprocal(samplingDistance));
  62. // Intel SSE2 does not have dynamic offset bit shifts, because it can only shift by constant bit offsets or dynamic byte offsets.
  63. // SSE2, AVX2 and NEON have low 16-bit unsigned multiplication.
  64. // _mm_mullo_epi16, _mm256_mullo_epi16 and vmulq_u16
  65. // Using lower bits might however not be enough and might take more time than simply shifting with scalar operations.
  66. // Then we might as well use SIMD comparisons and make bit masks the way to implement it on all platforms.
  67. // Because returning 1 can be used to return a mask as a fallback.
  68. // And one can also create many overloads for direct selection without the mask in between for future optimization.
  69. // Let textures created from images have 4 mip levels by default, and allow increasing the maximum depth with an optional argument.
  70. // Then make three comparisons to select a mip level.
  71. uint16_t mask = maskFromLeadingZeroes(density);
  72. // scale is a power of two 16-bit integer used to multiply uv coordinates.
  73. // But SSE2 also does not have 32-bit integer multiplication, so stay in 16 bits or use bit shifts!
  74. // Split into whole pixels and weights before the multiplication somehow.
  75. uint16_t scale = mask + 1;
  76. // Cast directly to uint16_t with saturation.
  77. tileXMask = texture.minimumWidth * scale;
  78. tileYMask = texture.minimumHeight * scale;
  79. startOffset = texture.startOffsetMask * scale * scale;
  80. }
  81. */
  82. #ifndef DFPSR_API_TEXTURE
  83. #define DFPSR_API_TEXTURE
  84. #include "../image/Texture.h"
  85. #include "../image/Image.h"
  86. #ifndef NDEBUG
  87. #include "../api/stringAPI.h"
  88. #endif
  89. #include "../base/DsrTraits.h"
  90. namespace dsr {
  91. // Post-condition: Returns true iff texture exists.
  92. inline bool texture_exists(const Texture &texture) { return texture.impl_buffer.isNotNull(); }
  93. // Post-condition: Returns the width in pixels for the highest resolution at mip level 0.
  94. inline int32_t texture_getMaxWidth(const Texture &texture) { return int32_t(1) << texture.impl_log2width; }
  95. // Post-condition: Returns the width in pixels for the resolution at mipLevel.
  96. inline int32_t texture_getWidth(const Texture &texture, uint32_t mipLevel) { return int32_t(1) << (texture.impl_log2width - mipLevel); }
  97. // Post-condition: Returns the height in pixels for the highest resolution at mip level 0.
  98. inline int32_t texture_getMaxHeight(const Texture &texture) { return int32_t(1) << texture.impl_log2height; }
  99. // Post-condition: Returns the height in pixels for the resolution at mipLevel.
  100. inline int32_t texture_getHeight(const Texture &texture, uint32_t mipLevel) { return int32_t(1) << (texture.impl_log2height - mipLevel); }
  101. // Get the maximum mip level, with zero overhead.
  102. // Post-condition: Returns an index to the highest mip level.
  103. inline int32_t texture_getSmallestMipLevel(const TextureRgbaU8& texture) { return texture.impl_maxMipLevel; }
  104. // Get the number of mip levels, or zero if the texture does not exist.
  105. // Useful for looping over all mip levels in a texture, by automatically skipping texture with no mip levels.
  106. // Post-condition: Returns the number of mip levels.
  107. inline int32_t texture_getMipLevelCount(const TextureRgbaU8& texture) { return texture_exists(texture) ? texture.impl_maxMipLevel + 1 : 0; }
  108. // Post-condition: Returns true iff texture has more than one mip level, so that updating the highest resolution needs to update lower layers.
  109. inline bool texture_hasPyramid(const Texture &texture) { return texture.impl_maxMipLevel != 0; }
  110. // Side-effect: Update all lower resolutions from the highest resolution using a basic linear average.
  111. void texture_generatePyramid(const TextureRgbaU8& texture);
  112. // mipLevel starts from 0 at the highest resolution and ends with the lowest resolution.
  113. // Pre-condition:
  114. // 0 <= mipLevel <= 15
  115. // Post-condition:
  116. // Returns the number of pixels from lower resolutions before the start of mipLevel.
  117. // Always returns 0 when there is only one mip level available.
  118. template<
  119. bool HIGHEST_RESOLUTION = false,
  120. typename U, // uint32_t, U32x4, U32x8, U32xX
  121. DSR_ENABLE_IF(DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U))>
  122. inline U texture_getPixelOffsetToLayer(const TextureRgbaU8 &texture, U mipLevel) {
  123. if (HIGHEST_RESOLUTION) {
  124. return U(texture.impl_startOffset);
  125. } else {
  126. return U(texture.impl_startOffset) & (U(texture.impl_maxLevelMask) >> bitShiftLeftImmediate<1>(mipLevel));
  127. }
  128. }
  129. // mipLevel starts from 0 at the highest resolution and ends with the lowest resolution.
  130. // Optimization arguments:
  131. // * SQUARE can be set to true when you know in compile time that texture has the same width and height.
  132. // * SINGLE_LAYER can be set to true when you know in compile time that there will only be a single resolution in the texture.
  133. // * XY_INSIDE can be set to true if you know that the pixel coordinates will always be within texture bounds (0 <= x < width, 0 <= y < height) without tiling.
  134. // * MIP_INSIDE can be set to true if you know that the mip level will always be within used indices (mipLevel <= texture_getSmallestMipLevel(texture)) without clamping.
  135. // Either way, mipLevel must always be within the 0..15 range, because dynamic bit shifting might truncate offsets that are too big.
  136. // * HIGHEST_RESOLUTION can be set to true if you want to ignore mipLevel and always sample the highest resolution at mipLevel 0.
  137. // Pre-condition:
  138. // mipLevel <= 15
  139. // Post-condition:
  140. // Returns the number of pixels before the pixel at (x, y) in mipLevel.
  141. template<
  142. bool SQUARE = false, // Width and height must be the same.
  143. bool SINGLE_LAYER = false, // Demanding that the texture only has a single layer.
  144. bool XY_INSIDE = false, // No pixels may be sampled outside.
  145. bool MIP_INSIDE = false, // Mip level may not go outside of existing layer indices.
  146. bool HIGHEST_RESOLUTION = false, // Ignoring any lower layers.
  147. typename U, // uint32_t, U32x4, U32x8, U32xX
  148. DSR_ENABLE_IF(DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U))>
  149. inline U texture_getPixelOffset(const TextureRgbaU8 &texture, U x, U y, U mipLevel) {
  150. // TODO: Reuse the tile masks when sampling a whole neighborhood for bi-linear sampling.
  151. // Clamp the mip-level using bitwise operations in a logarithmic scale, by masking out excess bits with zeroes and filling missing bits with ones.
  152. U tileMaskX = U(texture.impl_maxWidthAndMask );
  153. U tileMaskY = U(texture.impl_maxHeightAndMask);
  154. if (!HIGHEST_RESOLUTION) {
  155. tileMaskX = tileMaskX >> mipLevel;
  156. tileMaskY = tileMaskY >> mipLevel;
  157. }
  158. if (!MIP_INSIDE) {
  159. // If the mip level index might be higher than what is used in the texture, make sure that the tile masks have at least enough bits for the lowest texture resolution.
  160. tileMaskX = tileMaskX | texture.impl_minWidthOrMask;
  161. if (!SQUARE) {
  162. tileMaskY = tileMaskY | texture.impl_minHeightOrMask;
  163. }
  164. }
  165. U log2PixelStride = U(texture.impl_log2width);
  166. if (!HIGHEST_RESOLUTION) {
  167. log2PixelStride = log2PixelStride - mipLevel;
  168. }
  169. if (!XY_INSIDE) {
  170. x = x & tileMaskX;
  171. if (SQUARE) {
  172. // Apply the same mask to both for square images, so that the other mask can be optimized away.
  173. y = y & tileMaskX;
  174. } else {
  175. // Apply a separate mask for Y coordinates when the texture might not be square.
  176. y = y & tileMaskY;
  177. }
  178. }
  179. U coordinateOffset = ((y << log2PixelStride) | x);
  180. #ifndef NDEBUG
  181. // In debug mode, wrong use of optimization arguments will throw errors.
  182. if (SQUARE && (texture.impl_log2width != texture.impl_log2height)) {
  183. throwError(U"texture_getPixelOffset was told that the texture would have square dimensions using SQUARE, but ", texture_getMaxWidth(texture), U"x", texture_getMaxHeight(texture), U" is not square!\n");
  184. }
  185. if (SINGLE_LAYER && (texture_getSmallestMipLevel(texture) > 0)) {
  186. throwError(U"texture_getPixelOffset was told that the texture would only have a single layer using SINGLE_LAYER, but it has ", texture_getSmallestMipLevel(texture) + 1, U" layers!\n");
  187. }
  188. if (XY_INSIDE && !(allLanesEqual(x & ~tileMaskX, U(0)) && allLanesEqual(y & ~tileMaskY, U(0)))) {
  189. throwError(U"texture_getPixelOffset was told that the pixel coordinates would stay inside using XY_INSIDE, but the coordinate (", x, U", ", y, U") is not within", texture_getMaxWidth(texture), U"x", texture_getMaxHeight(texture), U" pixels!\n");
  190. }
  191. if (!HIGHEST_RESOLUTION) {
  192. if (!allLanesLesserOrEqual(mipLevel, U(15u))) {
  193. throwError(U"texture_getPixelOffset got mip level ", mipLevel, U", which is not within the fixed range of 0..15!\n");
  194. }
  195. if (MIP_INSIDE) {
  196. if (!allLanesLesserOrEqual(mipLevel, U(texture_getSmallestMipLevel(texture)))) {
  197. throwError(U"texture_getPixelOffset was told that the mip level would stay within valid indices using MIP_INSIDE, but mip level ", mipLevel, U" is not within 0..", texture_getSmallestMipLevel(texture), U"!\n");
  198. }
  199. }
  200. }
  201. #endif
  202. if (SINGLE_LAYER) {
  203. return coordinateOffset;
  204. } else {
  205. U startOffset = texture_getPixelOffsetToLayer<HIGHEST_RESOLUTION, U>(texture, mipLevel);
  206. return startOffset + coordinateOffset;
  207. }
  208. }
  209. template<
  210. bool SQUARE = false,
  211. bool SINGLE_LAYER = false,
  212. bool XY_INSIDE = false,
  213. bool MIP_INSIDE = false,
  214. bool HIGHEST_RESOLUTION = false,
  215. typename U, // uint32_t, U32x4, U32x8, U32xX
  216. DSR_ENABLE_IF(DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U))>
  217. inline U texture_readPixel(const TextureRgbaU8 &texture, U x, U y, U mipLevel) {
  218. #ifndef NDEBUG
  219. if (!texture_exists(texture)) {
  220. throwError(U"Tried to read pixels from a texture that does not exist!\n");
  221. }
  222. if (!HIGHEST_RESOLUTION) {
  223. if (!allLanesLesserOrEqual(mipLevel, U(15u))) {
  224. throwError(U"Tried to read pixels from mip level ", mipLevel, U", which is outside of the allowed 4-bit range 0..4!\n");
  225. }
  226. }
  227. #endif
  228. SafePointer<uint32_t> data = texture.impl_buffer.getSafe<uint32_t>("RgbaU8 pyramid pixel buffer for pixel reading");
  229. return gather_U32(data, texture_getPixelOffset<SQUARE, SINGLE_LAYER, XY_INSIDE, MIP_INSIDE, HIGHEST_RESOLUTION, U>(texture, x, y, mipLevel));
  230. }
  231. // Pre-condition:
  232. // 0 <= mipLevel <= 15
  233. inline void texture_writePixel(const TextureRgbaU8 &texture, uint32_t x, uint32_t y, uint32_t mipLevel, uint32_t packedColor) {
  234. #ifndef NDEBUG
  235. if (!texture_exists(texture)) {
  236. throwError(U"Tried to write a pixel to a texture that does not exist!\n");
  237. }
  238. if (mipLevel > 15u) {
  239. throwError(U"Tried to write a pixel to mip level ", mipLevel, U", which is outside of the allowed 4-bit range 0..4!\n");
  240. }
  241. #endif
  242. SafePointer<uint32_t> data = texture.impl_buffer.getSafe<uint32_t>("RgbaU8 pyramid pixel buffer for pixel writing");
  243. data[texture_getPixelOffset<false, false, false, false, false, uint32_t>(texture, x, y, mipLevel)] = packedColor;
  244. }
  245. // TODO: Use these template arguments in RgbaMultiply.h to improve performance for square textures with at least 4 mip levels and UV coordinates inside of the texture.
  246. // TODO: Can EXISTS be an argument to disable when non-existing images should be replaced with U(255u) for fast prototyping?
  247. // Sample the nearest pixel in a normalized UV scale where one unit equals one lap around the image.
  248. // Pre-condition:
  249. // 0.0f <= u, 0.0f <= v
  250. // Negative texture coordinates are not allowed, because they are converted to unsigned integers for bitwise operations.
  251. template<
  252. bool SQUARE = false,
  253. bool SINGLE_LAYER = false,
  254. bool MIP_INSIDE = false,
  255. bool HIGHEST_RESOLUTION = false,
  256. typename U, // uint32_t, U32x4, U32x8, U32xX
  257. typename F, // float, F32x4, F32x8, F32xX, F32xF
  258. DSR_ENABLE_IF(DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U) && DSR_CHECK_PROPERTY(DsrTrait_Any_F32, F))>
  259. inline U texture_sample_nearest(const TextureRgbaU8 &texture, F u, F v, U mipLevel) {
  260. U scaleU = U(1u) << U(texture.impl_log2width );
  261. U scaleV = U(1u) << U(texture.impl_log2height);
  262. if (!HIGHEST_RESOLUTION) {
  263. scaleU = scaleU >> mipLevel;
  264. scaleV = scaleV >> mipLevel;
  265. }
  266. U xPixel = truncateToU32(u * floatFromU32(scaleU));
  267. U yPixel = truncateToU32(v * floatFromU32(scaleV));
  268. return texture_readPixel<SQUARE, SINGLE_LAYER, false, MIP_INSIDE, HIGHEST_RESOLUTION, U>(texture, xPixel, yPixel, mipLevel);
  269. }
  270. // Returns (colorA * weightA + colorB * weightB) / 256 as bytes
  271. // weightA and weightB should contain pairs of the same 16-bit weights for each of the 4 pixels in the corresponding A and B colors
  272. template <typename U32, typename U16, DSR_ENABLE_IF(
  273. DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U32) &&
  274. DSR_CHECK_PROPERTY(DsrTrait_Any_U16, U16)
  275. )>
  276. inline U32 weightColors(const U32 &colorA, const U16 &weightA, const U32 &colorB, const U16 &weightB) {
  277. U32 lowMask(0x00FF00FFu);
  278. U16 lowColorA = reinterpret_U16FromU32(colorA & lowMask);
  279. U16 lowColorB = reinterpret_U16FromU32(colorB & lowMask);
  280. U32 highMask(0xFF00FF00u);
  281. U16 highColorA = reinterpret_U16FromU32(bitShiftRightImmediate<8>(colorA & highMask));
  282. U16 highColorB = reinterpret_U16FromU32(bitShiftRightImmediate<8>(colorB & highMask));
  283. U32 lowColor = reinterpret_U32FromU16(((lowColorA * weightA) + (lowColorB * weightB)));
  284. U32 highColor = reinterpret_U32FromU16(((highColorA * weightA) + (highColorB * weightB)));
  285. return ((bitShiftRightImmediate<8>(lowColor) & lowMask) | (highColor & highMask));
  286. }
  287. // The more significant bits must be zero so that the lower bits can fill the space.
  288. // lowBits[x] < 2^16
  289. template <typename U32, DSR_ENABLE_IF(
  290. DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U32)
  291. )>
  292. inline auto repeatAs16Bits(const U32 &lowBits) {
  293. return reinterpret_U16FromU32(lowBits | bitShiftLeftImmediate<16>(lowBits));
  294. }
  295. // Returns 256 - weight
  296. template <typename U16, DSR_ENABLE_IF(
  297. DSR_CHECK_PROPERTY(DsrTrait_Any_U16, U16)
  298. )>
  299. inline U16 invertWeight(const U16 &weight) {
  300. return U16(0x01000100u) - weight;
  301. }
  302. /* TODO: Use for anisotropic or tri-linear sampling.
  303. template <typename U32, typename U16>
  304. inline U32 mix_L(const U32 &colorA, const U32 &colorB, const U32 &weight) {
  305. // Get inverse weights
  306. U16 weightB = repeatAs16Bits(weight);
  307. U16 weightA = invertWeight(weightB);
  308. // Multiply
  309. return weightColors(colorA, weightA, colorB, weightB);
  310. }
  311. */
  312. template <typename U32, typename U16>
  313. inline U32 mix_BL(const U32 &colorA, const U32 &colorB, const U32 &colorC, const U32 &colorD, const U32 &weightX, const U32 &weightY) {
  314. // Get inverse weights
  315. U16 weightXR = repeatAs16Bits<U32>(weightX);
  316. U16 weightYB = repeatAs16Bits<U32>(weightY);
  317. U16 weightXL = invertWeight<U16>(weightXR);
  318. U16 weightYT = invertWeight<U16>(weightYB);
  319. // Multiply
  320. return weightColors<U32, U16>(weightColors(colorA, weightXL, colorB, weightXR), weightYT, weightColors(colorC, weightXL, colorD, weightXR), weightYB);
  321. }
  322. template<
  323. bool SQUARE = false,
  324. bool SINGLE_LAYER = false,
  325. bool MIP_INSIDE = false,
  326. bool HIGHEST_RESOLUTION = false,
  327. typename U32, // uint32_t, U32x4, U32x8, U32xX
  328. typename U16, // uint32_t, U32x4, U32x8, U32xX
  329. typename F32, // float, F32x4, F32x8, F32xX, F32xF
  330. DSR_ENABLE_IF(
  331. DSR_CHECK_PROPERTY(DsrTrait_Any_U32, U32) &&
  332. DSR_CHECK_PROPERTY(DsrTrait_Any_U16, U16) &&
  333. DSR_CHECK_PROPERTY(DsrTrait_Any_F32, F32)
  334. )>
  335. inline U32 texture_sample_bilinear(const TextureRgbaU8 &texture, F32 u, F32 v, U32 mipLevel) {
  336. U32 scaleU = U32(256u) << U32(texture.impl_log2width );
  337. U32 scaleV = U32(256u) << U32(texture.impl_log2height);
  338. if (!HIGHEST_RESOLUTION) {
  339. scaleU = scaleU >> mipLevel;
  340. scaleV = scaleV >> mipLevel;
  341. }
  342. // Convert from the normalized 0..1 scale to a 0..size*256 scale for 8 bits of sub-pixel precision.
  343. // Half a pixel is subtracted so that the seam between bi-linear patches end up at the center of texels.
  344. U32 subCenterX = truncateToU32(u * floatFromU32(scaleU)) - U32(128);
  345. U32 subCenterY = truncateToU32(v * floatFromU32(scaleV)) - U32(128);
  346. // Get the remainders as interpolation weights.
  347. U32 weightX = subCenterX & 0xFF;
  348. U32 weightY = subCenterY & 0xFF;
  349. // Divide and truncate sub-pixel coordinates to get whole pixel coordinates.
  350. U32 pixelLeft = bitShiftRightImmediate<8>(subCenterX);
  351. U32 pixelTop = bitShiftRightImmediate<8>(subCenterY);
  352. U32 pixelRight = pixelLeft + 1;
  353. U32 pixelBottom = pixelTop + 1;
  354. // Generate pixel tiling masks.
  355. U32 tileMaskX = U32(texture.impl_maxWidthAndMask );
  356. U32 tileMaskY = U32(texture.impl_maxHeightAndMask);
  357. if (!HIGHEST_RESOLUTION) {
  358. tileMaskX = tileMaskX >> mipLevel;
  359. tileMaskY = tileMaskY >> mipLevel;
  360. }
  361. if (!MIP_INSIDE) {
  362. tileMaskX = tileMaskX | texture.impl_minWidthOrMask;
  363. if (!SQUARE) {
  364. tileMaskY = tileMaskY | texture.impl_minHeightOrMask;
  365. }
  366. }
  367. // Get the stride.
  368. U32 log2PixelStride = U32(texture.impl_log2width);
  369. if (!HIGHEST_RESOLUTION) {
  370. log2PixelStride = log2PixelStride - mipLevel;
  371. }
  372. // Apply tiling masks
  373. pixelLeft = pixelLeft & tileMaskX;
  374. pixelRight = pixelRight & tileMaskX;
  375. if (SQUARE) {
  376. // Apply the same mask to both for square images, so that the other mask can be optimized away.
  377. pixelTop = pixelTop & tileMaskX;
  378. pixelBottom = pixelBottom & tileMaskX;
  379. } else {
  380. // Apply a separate mask for Y coordinates when the texture might not be square.
  381. pixelTop = pixelTop & tileMaskY;
  382. pixelBottom = pixelBottom & tileMaskY;
  383. }
  384. #ifndef NDEBUG
  385. // In debug mode, wrong use of optimization arguments will throw errors.
  386. if (SQUARE && (texture.impl_log2width != texture.impl_log2height)) {
  387. throwError(U"texture_getPixelOffset was told that the texture would have square dimensions using SQUARE, but ", texture_getMaxWidth(texture), U"x", texture_getMaxHeight(texture), U" is not square!\n");
  388. }
  389. if (SINGLE_LAYER && (texture_getSmallestMipLevel(texture) > 0)) {
  390. throwError(U"texture_getPixelOffset was told that the texture would only have a single layer using SINGLE_LAYER, but it has ", texture_getSmallestMipLevel(texture) + 1, U" layers!\n");
  391. }
  392. if (!HIGHEST_RESOLUTION) {
  393. if (!allLanesLesserOrEqual(mipLevel, U32(15u))) {
  394. throwError(U"texture_getPixelOffset got mip level ", mipLevel, U", which is not within the fixed range of 0..15!\n");
  395. }
  396. if (MIP_INSIDE) {
  397. if (!allLanesLesserOrEqual(mipLevel, U32(texture_getSmallestMipLevel(texture)))) {
  398. throwError(U"texture_getPixelOffset was told that the mip level would stay within valid indices using MIP_INSIDE, but mip level ", mipLevel, U" is not within 0..", texture_getSmallestMipLevel(texture), U"!\n");
  399. }
  400. }
  401. }
  402. #endif
  403. U32 upperOffset = pixelTop << log2PixelStride;
  404. U32 bottomOffset = pixelBottom << log2PixelStride;
  405. U32 upperLeftOffset = upperOffset | pixelLeft;
  406. U32 upperRightOffset = upperOffset | pixelRight;
  407. U32 bottomLeftOffset = bottomOffset | pixelLeft;
  408. U32 bottomRightOffset = bottomOffset | pixelRight;
  409. if (!SINGLE_LAYER) {
  410. U32 layerStartOffset = texture_getPixelOffsetToLayer<HIGHEST_RESOLUTION, U32>(texture, mipLevel);
  411. upperLeftOffset = upperLeftOffset + layerStartOffset;
  412. upperRightOffset = upperRightOffset + layerStartOffset;
  413. bottomLeftOffset = bottomLeftOffset + layerStartOffset;
  414. bottomRightOffset = bottomRightOffset + layerStartOffset;
  415. }
  416. SafePointer<uint32_t> data = texture.impl_buffer.getSafe<uint32_t>("RgbaU8 pyramid pixel buffer for bi-linear pixel sampling");
  417. U32 upperLeftColor = gather_U32(data, upperLeftOffset );
  418. U32 upperRightColor = gather_U32(data, upperRightOffset );
  419. U32 bottomLeftColor = gather_U32(data, bottomLeftOffset );
  420. U32 bottomRightColor = gather_U32(data, bottomRightOffset);
  421. return mix_BL<U32, U16>(upperLeftColor, upperRightColor, bottomLeftColor, bottomRightColor, weightX, weightY);
  422. }
  423. // resolutions is the maximum number of resolutions to create.
  424. // The actual number of layers in the texture is limited by the most narrow dimension.
  425. // A texture of 16x4 pixels can have up to three resolutions, 4x1, 8x2 and 16x4.
  426. // A texture of 8x8 pixels can have up to four resolutions, 1x1, 2x2, 4x4 and 8x8.
  427. // Pre-condition:
  428. // 1 <= width <= 32768
  429. // 1 <= height <= 32768
  430. // 0 <= resolutions <= 16
  431. // Post-condition:
  432. // Returns a pyramid image of the smallest power of two size capable of storing width x height pixels, by scaling up the resolution with interpolation if needed.
  433. TextureRgbaU8 texture_create_RgbaU8(int32_t width, int32_t height, int32_t resolutions);
  434. // Pre-condition:
  435. // 1 <= width <= 32768
  436. // 1 <= height <= 32768
  437. // 1 <= resolutions
  438. // Post-condition:
  439. // Returns a pyramid image created from image, or an empty pyramid if the image is empty.
  440. TextureRgbaU8 texture_create_RgbaU8(const ImageRgbaU8& image, int32_t resolutions);
  441. // Get a layer from the texture as an image.
  442. // Pre-condition:
  443. // texture_exists(texture)
  444. // 0 <= mipLevel <= texture_getSmallestMipLevel(texture)
  445. // Post-condition:
  446. // Returns an unaligned RGBA image sharing pixel data with the requested texture layer.
  447. ImageRgbaU8 texture_getMipLevelImage(const TextureRgbaU8& texture, int32_t mipLevel);
  448. // TODO: Pre-calculate the pixel offset, float scales and tile masks and merge into a reusable multi-layer sampling method.
  449. // Because dynamic bit shifts can not be vectorized on Intel processors and would be the same for 2x2 pixels anyway.
  450. // The hard part will be to implement it for ARM SVE with variable width vectors, so maybe calculate the
  451. // 2x2 derivation sparsely, interpolate a floating mip level and do comparisons vectorized with blend instructions to select masks.
  452. template <typename F>
  453. inline uint32_t texture_getMipLevelIndex(const TextureRgbaU8 &source, const F &u, const F &v) {
  454. // TODO: Support reading elements from SIMD vectors of any size somehow. Can use SVE's maximum size of 2048 bits as the space to allocate in advance to aligned stack memory.
  455. // Assume that U is at least 128 bits wide and reuse the result for additional pixels if there is more.
  456. auto ua = u.get();
  457. auto va = v.get();
  458. float offsetUX = fabs(ua.x - ua.y); // Left U - Right U
  459. float offsetUY = fabs(ua.x - ua.z); // Top U - Bottom U
  460. float offsetVX = fabs(va.x - va.y); // Left V - Right V
  461. float offsetVY = fabs(va.x - va.z); // Top V - Bottom V
  462. float offsetU = max(offsetUX, offsetUY) * source.impl_floatMaxWidth;
  463. float offsetV = max(offsetVX, offsetVY) * source.impl_floatMaxHeight;
  464. float offset = max(offsetU, offsetV);
  465. int result = 0;
  466. // TODO: Can count leading zeroes be used with integers to use all available mip levels?
  467. // It would make MIP_INSIDE useless for optimization.
  468. if (offset > 2.0f) { result = 1; }
  469. if (offset > 4.0f) { result = 2; }
  470. if (offset > 8.0f) { result = 3; }
  471. if (offset > 16.0f) { result = 4; }
  472. // TODO: Should it be possible to configure the number of mip levels?
  473. return result;
  474. }
  475. // TODO: Optimize using template arguments.
  476. // Pre-conditions:
  477. // 0 <= mipLevel <= texture_getSmallestMipLevel(texture)
  478. // Post-condition:
  479. // Returns a safe pointer to the first pixel at mipLevel in texture.
  480. template <typename U = uint32_t>
  481. inline SafePointer<U> texture_getSafePointer(const TextureRgbaU8& texture, uint32_t mipLevel) {
  482. // Get a pointer to the start of the image.
  483. return texture.impl_buffer.getSafe<U>("RgbaU8 pyramid pixel buffer").increaseBytes(texture_getPixelOffsetToLayer(texture, mipLevel) * sizeof(uint32_t));
  484. }
  485. // TODO: Optimize using template arguments.
  486. // Pre-conditions:
  487. // 0 <= mipLevel <= texture_getSmallestMipLevel(texture)
  488. // 0 <= rowIndex < (1 << mipLevel)
  489. // Post-condition:
  490. // Returns a safe pointer to the first pixel at rowIndex in mipLevel in texture.
  491. template <typename U = uint32_t>
  492. inline SafePointer<U> texture_getSafePointer(const TextureRgbaU8& texture, int32_t mipLevel, int32_t rowIndex) {
  493. return texture_getSafePointer<U>(texture, mipLevel).increaseBytes(texture_getWidth(texture, mipLevel) * sizeof(uint32_t) * rowIndex);
  494. }
  495. }
  496. #endif