1 /++ 2 + Authors: Avaxar <avaxar@nekkl.org> 3 + Copyright: Copyright © 2023, Avaxar 4 + License: $(LINK2 https://mit-license.org, MIT License) 5 +/ 6 7 module dsdl2.texture; 8 @safe: 9 10 import bindbc.sdl; 11 import dsdl2.sdl; 12 import dsdl2.blend; 13 import dsdl2.rect; 14 import dsdl2.frect; 15 import dsdl2.pixels; 16 import dsdl2.renderer; 17 import dsdl2.surface; 18 19 import core.memory : GC; 20 import std.conv : to; 21 import std.format : format; 22 23 /++ 24 + D enum that wraps `SDL_TextureAccess` in specifying texture access mode 25 +/ 26 enum TextureAccess { 27 /++ 28 + Wraps `SDL_TEXTUREACCESS_*` enumeration constants 29 +/ 30 static_ = SDL_TEXTUREACCESS_STATIC, 31 streaming = SDL_TEXTUREACCESS_STREAMING, /// ditto 32 target = SDL_TEXTUREACCESS_TARGET /// ditto 33 } 34 35 static if (sdlSupport >= SDLSupport.v2_0_12) { 36 /++ 37 + D enum that wraps `SDL_ScaleMode` in specifying mode of texture scaling 38 +/ 39 enum ScaleMode { 40 /++ 41 + Wraps `SDL_ScaleMode*` enumeration constants 42 +/ 43 nearest = SDL_ScaleModeNearest, 44 linear = SDL_ScaleModeLinear, /// ditto 45 best = SDL_ScaleModeBest /// ditto 46 } 47 } 48 49 /++ 50 + D class that wraps `SDL_Texture` storing textures in the VRAM 51 + 52 + `dsdl2.Texture` stores a 2D image out of pixels with a `width` and `height`, where each pixel stored in the 53 + GPU RAM (VRAM) according to its defined `dsdl2.PixelFormat`. A `dsdl2.Texture` is associated with its 54 + `dsdl2.Renderer`, and can only be operated with/by it. 55 + 56 + Example: 57 + --- 58 + auto renderer = new dsdl2.Renderer(...); 59 + auto surface = new dsdl2.Surface(...); 60 + 61 + auto texture = new dsdl2.Texture(renderer, surface); 62 + --- 63 +/ 64 final class Texture { 65 private PixelFormat pixelFormatProxy = null; 66 private bool isOwner = true; 67 private void* userRef = null; 68 69 @system SDL_Texture* sdlTexture; /// Internal `SDL_Texture` pointer 70 71 /++ 72 + Constructs a `dsdl2.Texture` from a vanilla `SDL_Texture*` from bindbc-sdl 73 + 74 + Params: 75 + sdlTexture = the `SDL_Texture` pointer to manage 76 + isOwner = whether the instance owns the given `SDL_Texture*` and should destroy it on its own 77 + userRef = optional pointer to maintain reference link, avoiding GC cleanup 78 +/ 79 this(SDL_Texture* sdlTexture, bool isOwner = true, void* userRef = null) @system 80 in { 81 assert(sdlTexture !is null); 82 } 83 do { 84 this.sdlTexture = sdlTexture; 85 this.isOwner = isOwner; 86 this.userRef = userRef; 87 } 88 89 /++ 90 + Creates a blank `dsdl2.Texture` in the VRAM, which wraps `SDL_CreateTexture` 91 + 92 + Params: 93 + renderer = `dsdl2.Renderer` the texture belongs to 94 + pixelFormat = `dsdl2.PixelFormat` that the texture pixel data is stored as 95 + access = `dsdl2.TextureAccess` enumeration which indicates its access rule 96 + size = the size of the texture (width and height) 97 + Throws: `dsdl2.SDLException` if creation failed 98 +/ 99 this(Renderer renderer, PixelFormat pixelFormat, TextureAccess access, uint[2] size) @trusted 100 in { 101 assert(renderer !is null); 102 assert(pixelFormat !is null); 103 } 104 do { 105 this.sdlTexture = SDL_CreateTexture(renderer.sdlRenderer, pixelFormat.sdlPixelFormatEnum, access, 106 size[0].to!int, size[1].to!int); 107 if (this.sdlTexture is null) { 108 throw new SDLException; 109 } 110 } 111 112 /++ 113 + Creates a `dsdl2.Texture` in the VRAM from a `dsdl2.Surface`, which wraps `SDL_CreateTextureFromSurface` 114 + 115 + Params: 116 + renderer = `dsdl2.Renderer` the texture belongs to 117 + surface = `dsdl2.Surface` for its pixel data to be copied over to the texture 118 + Throws: `dsdl2.SDLException` if creation failed 119 +/ 120 this(Renderer renderer, Surface surface) @trusted 121 in { 122 assert(renderer !is null); 123 assert(surface !is null); 124 } 125 do { 126 this.sdlTexture = SDL_CreateTextureFromSurface(renderer.sdlRenderer, surface.sdlSurface); 127 if (this.sdlTexture is null) { 128 throw new SDLException; 129 } 130 } 131 132 ~this() @trusted { 133 if (this.isOwner) { 134 SDL_DestroyTexture(this.sdlTexture); 135 } 136 } 137 138 @trusted invariant { // @suppress(dscanner.trust_too_much) 139 // Instance might be in an invalid state due to holding a non-owned externally-freed object when 140 // destructed in an unpredictable order. 141 if (!this.isOwner && GC.inFinalizer) { 142 return; 143 } 144 145 assert(this.sdlTexture !is null); 146 } 147 148 /++ 149 + Equality operator overload 150 +/ 151 bool opEquals(const Texture rhs) const @trusted { 152 return this.sdlTexture is rhs.sdlTexture; 153 } 154 155 /++ 156 + Gets the hash of the `dsdl2.Texture` 157 + 158 + Returns: unique hash for the instance being the pointer of the internal `SDL_Texture` pointer 159 +/ 160 override hash_t toHash() const @trusted { 161 return cast(hash_t) this.sdlTexture; 162 } 163 164 /++ 165 + Formats the `dsdl2.Texture` into its construction representation: `"dsdl2.Texture(<sdlTexture>)"` 166 + 167 + Returns: the formatted `string` 168 +/ 169 override string toString() const @trusted { 170 return "dsdl2.Texture(0x%x)".format(this.sdlTexture); 171 } 172 173 /++ 174 + Wraps `SDL_QueryTexture` which gets the `dsdl2.PixelFormat` of the `dsdl2.Texture` 175 + 176 + Returns: `const` proxy to the `dsdl2.PixelFormat` of the `dsdl2.Texture` 177 + Throws: `dsdl2.SDLException` if failed to query the information 178 +/ 179 const(PixelFormat) pixelFormat() const @property @trusted { 180 uint sdlPixelFormatEnum = void; 181 if (SDL_QueryTexture(cast(SDL_Texture*) this.sdlTexture, &sdlPixelFormatEnum, null, null, null) != 0) { 182 throw new SDLException; 183 } 184 185 // If the actual pixel format happens to change, rewire the proxy. 186 if (this.pixelFormatProxy.sdlPixelFormatEnum != sdlPixelFormatEnum) { 187 (cast(Texture) this).pixelFormatProxy = new PixelFormat(sdlPixelFormatEnum); 188 } 189 190 return this.pixelFormatProxy; 191 } 192 193 /++ 194 + Wraps `SDL_QueryTexture` which gets the `dsdl2.TextureAccess` of the `dsdl2.Texture` 195 + 196 + Returns: `dsdl2.TextureAccess` of the `dsdl2.Texture` 197 + Throws: `dsdl2.SDLException` if failed to query the information 198 +/ 199 TextureAccess access() const @property @trusted { 200 TextureAccess texAccess = void; 201 if (SDL_QueryTexture(cast(SDL_Texture*) this.sdlTexture, null, cast(int*) texAccess, null, null) != 0) { 202 throw new SDLException; 203 } 204 205 return texAccess; 206 } 207 208 /++ 209 + Wraps `SDL_QueryTexture` which gets the width of the `dsdl2.Texture` in pixels 210 + 211 + Returns: width of the `dsdl2.Texture` in pixels 212 + Throws: `dsdl2.SDLException` if failed to query the information 213 +/ 214 uint width() const @property @trusted { 215 uint w = void; 216 if (SDL_QueryTexture(cast(SDL_Texture*) this.sdlTexture, null, null, cast(int*)&w, null) != 0) { 217 throw new SDLException; 218 } 219 220 return w; 221 } 222 223 /++ 224 + Wraps `SDL_QueryTexture` which gets the height of the `dsdl2.Texture` in pixels 225 + 226 + Returns: height of the `dsdl2.Texture` in pixels 227 + Throws: `dsdl2.SDLException` if failed to query the information 228 +/ 229 uint height() const @property @trusted { 230 uint h = void; 231 if (SDL_QueryTexture(cast(SDL_Texture*) this.sdlTexture, null, null, null, cast(int*)&h) != 0) { 232 throw new SDLException; 233 } 234 235 return h; 236 } 237 238 /++ 239 + Wraps `SDL_QueryTexture` which gets the size of the `dsdl2.Texture` in pixels 240 + 241 + Returns: array of width and height of the `dsdl2.Texture` in pixels 242 + Throws: `dsdl2.SDLException` if failed to query the information 243 +/ 244 uint[2] size() const @property @trusted { 245 uint[2] wh = void; 246 if (SDL_QueryTexture(cast(SDL_Texture*) this.sdlTexture, null, null, cast(int*)&wh[0], 247 cast(int*)&wh[1]) != 0) { 248 throw new SDLException; 249 } 250 251 return wh; 252 } 253 254 /++ 255 + Gets the color and alpha multipliers of the `dsdl2.Texture` that wraps `SDL_GetTextureColorMod` and 256 + `SDL_GetTextureAlphaMod` 257 + 258 + Returns: color and alpha multipliers of the `dsdl2.Texture` 259 +/ 260 Color mod() const @property @trusted { 261 Color multipliers = void; 262 SDL_GetTextureColorMod(cast(SDL_Texture*) this.sdlTexture, &multipliers.sdlColor.r, 263 &multipliers.sdlColor.g, &multipliers.sdlColor.b); 264 SDL_GetTextureAlphaMod(cast(SDL_Texture*) this.sdlTexture, &multipliers.sdlColor.a); 265 return multipliers; 266 } 267 268 /++ 269 + Sets the color and alpha multipliers of the `dsdl2.Texture` that wraps `SDL_SetTextureColorMod` and 270 + `SDL_SetTextureAlphaMod` 271 + 272 + Params: 273 + newMod = `dsdl2.Color` with `.r`, `.g`, `.b` as the color multipliers, and `.a` as the alpha multiplier 274 +/ 275 void mod(Color newMod) @property @trusted { 276 SDL_SetTextureColorMod(this.sdlTexture, newMod.r, newMod.g, newMod.b); 277 SDL_SetTextureAlphaMod(this.sdlTexture, newMod.a); 278 } 279 280 /++ 281 + Wraps `SDL_GetTextureColorMod` which gets the color multipliers of the `dsdl2.Texture` 282 + 283 + Returns: color multipliers of the `dsdl2.Texture` 284 +/ 285 ubyte[3] colorMod() const @property @trusted { 286 ubyte[3] rgbMod = void; 287 SDL_GetTextureColorMod(cast(SDL_Texture*) this.sdlTexture, &rgbMod[0], &rgbMod[1], &rgbMod[2]); 288 return rgbMod; 289 } 290 291 /++ 292 + Wraps `SDL_SetTextureColorMod` which sets the color multipliers of the `dsdl2.Texture` 293 + 294 + Params: 295 + newColorMod = an array of `ubyte`s representing red, green, and blue multipliers (each 0-255) 296 +/ 297 void colorMod(ubyte[3] newColorMod) @property @trusted { 298 SDL_SetTextureColorMod(this.sdlTexture, newColorMod[0], newColorMod[1], newColorMod[2]); 299 } 300 301 /++ 302 + Wraps `SDL_GetTextureAlphaMod` which gets the alpha multiplier of the `dsdl2.Texture` 303 + 304 + Returns: alpha multiplier of the `dsdl2.Texture` 305 +/ 306 ubyte alphaMod() const @property @trusted { 307 ubyte aMod = void; 308 SDL_GetTextureAlphaMod(cast(SDL_Texture*) this.sdlTexture, &aMod); 309 return aMod; 310 } 311 312 /++ 313 + Wraps `SDL_SetTextureAlphaMod` which sets the alpha multiplier of the `dsdl2.Texture` 314 + 315 + Params: 316 + newAlphaMod = alpha multiplier (0-255) 317 +/ 318 void alphaMod(ubyte newAlphaMod) @property @trusted { 319 SDL_SetTextureAlphaMod(this.sdlTexture, newAlphaMod); 320 } 321 322 /++ 323 + Wraps `SDL_GetTextureBlendMode` which gets the `dsdl2.Texture`'s `dsdl2.BlendMode` defining drawing 324 + 325 + Returns: `dsdl2.BlendMode` of the `dsdl2.Texture` 326 + Throws: `dsdl2.SDLException` if `dsdl2.BlendMode` unable to get 327 +/ 328 BlendMode blendMode() const @property @trusted { 329 BlendMode mode = void; 330 if (SDL_GetTextureBlendMode(cast(SDL_Texture*) this.sdlTexture, &mode.sdlBlendMode) != 0) { 331 throw new SDLException; 332 } 333 334 return mode; 335 } 336 337 /++ 338 + Wraps `SDL_SetTextureBlendMode` which sets the `dsdl2.Texture`'s `dsdl2.BlendMode` defining drawing 339 + 340 + Params: 341 + newMode = `dsdl2.BlendMode` to set 342 + Throws: `dsdl2.SDLException` if `dsdl2.BlendMode` unable to set 343 +/ 344 void blendMode(BlendMode newMode) @property @trusted { 345 if (SDL_SetTextureBlendMode(this.sdlTexture, newMode.sdlBlendMode) != 0) { 346 throw new SDLException; 347 } 348 } 349 350 /++ 351 + Wraps `SDL_UpdateTexture` which updates the entire `dsdl2.Texture`'s pixel data 352 + 353 + Params: 354 + pixels = array of pixels for the entire `dsdl2.Texture`'s pixels to be replaced with 355 + pitch = skips in bytes per line/row of the `pixels` 356 + Throws: `dsdl2.SDLException` if failed to update the texture pixel data 357 +/ 358 void update(const void[] pixels, size_t pitch) @trusted 359 in { 360 assert(pitch * 8 >= this.width * this.pixelFormat.bitsPerPixel); 361 assert(pixels.length == pitch * this.height); 362 } 363 do { 364 if (SDL_UpdateTexture(this.sdlTexture, null, pixels.ptr, pitch.to!int) != 0) { 365 throw new SDLException; 366 } 367 } 368 369 /++ 370 + Wraps `SDL_UpdateTexture` which updates the `dsdl2.Texture`'s pixel data at a certain `dsdl2.Rect` boundary 371 + 372 + Params: 373 + rect = `dsdl2.Rect` boundary marking the part of the texture whose pixels are to be updated 374 + pixels = array of pixels for the `dsdl2.Texture`'s `rect` pixels to be replaced with 375 + pitch = skips in bytes per line/row of the `pixels` 376 + Throws: `dsdl2.SDLException` if failed to update the texture pixel data 377 +/ 378 void update(Rect rect, void[] pixels, size_t pitch) @trusted 379 in { 380 assert(pitch * 8 >= rect.width * this.pixelFormat.bitsPerPixel); 381 assert(pixels.length == pitch * rect.height); 382 } 383 do { 384 if (SDL_UpdateTexture(this.sdlTexture, &rect.sdlRect, pixels.ptr, pitch.to!int) != 0) { 385 throw new SDLException; 386 } 387 } 388 389 /++ 390 + Wraps `SDL_GL_BindTexture` which binds the texture in OpenGL 391 + 392 + Returns: texture width and height in OpenGL 393 + Throws: `dsdl2.SDLException` if failed to bind 394 +/ 395 float[2] bindGL() @trusted { 396 float[2] size = void; 397 if (SDL_GL_BindTexture(this.sdlTexture, &size[0], &size[1]) != 0) { 398 throw new SDLException; 399 } 400 401 return size; 402 } 403 404 /++ 405 + Wraps `SDL_GL_UnbindTexture` which unbinds the texture in OpenGL 406 + 407 + Throws: `dsdl2.SDLException` if failed to unbind 408 +/ 409 void unbindGL() @trusted { 410 if (SDL_GL_UnbindTexture(this.sdlTexture) != 0) { 411 throw new SDLException; 412 } 413 } 414 415 static if (sdlSupport >= SDLSupport.v2_0_12) { 416 /++ 417 + Wraps `SDL_GetTextureScaleMode` (from SDL 2.0.12) which gets the texture's scaling mode 418 + 419 + Returns: `dsdl2.ScaleMode` of the texture 420 +/ 421 ScaleMode scaleMode() const @property @trusted 422 in { 423 assert(getVersion() >= Version(2, 0, 12)); 424 } 425 do { 426 SDL_ScaleMode sdlScaleMode = void; 427 if (SDL_GetTextureScaleMode(cast(SDL_Texture*) this.sdlTexture, &sdlScaleMode) != 0) { 428 throw new SDLException; 429 } 430 431 return cast(ScaleMode) sdlScaleMode; 432 } 433 434 /++ 435 + Wraps `SDL_SetTextureScaleMode` (from SDL 2.0.12) which sets the texture's scaling mode 436 + 437 + Params: 438 + newMode = new `dsdl2.ScaleMode` set 439 +/ 440 void scaleMode(ScaleMode newMode) @property @trusted 441 in { 442 assert(getVersion() >= Version(2, 0, 12)); 443 } 444 do { 445 if (SDL_SetTextureScaleMode(this.sdlTexture, cast(SDL_ScaleMode) newMode) != 0) { 446 throw new SDLException; 447 } 448 } 449 } 450 }