1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21// LOVE
22#include "common/config.h"
23#include "Texture.h"
24#include "Graphics.h"
25
26// C
27#include <cmath>
28#include <algorithm>
29
30
31namespace love
32{
33namespace graphics
34{
35
36love::Type Texture::type("Texture", &Drawable::type);
37
38Texture::Filter Texture::defaultFilter;
39Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR;
40float Texture::defaultMipmapSharpness = 0.0f;
41int64 Texture::totalGraphicsMemory = 0;
42
43Texture::Texture(TextureType texType)
44 : texType(texType)
45 , format(PIXELFORMAT_UNKNOWN)
46 , readable(true)
47 , width(0)
48 , height(0)
49 , depth(1)
50 , layers(1)
51 , mipmapCount(1)
52 , pixelWidth(0)
53 , pixelHeight(0)
54 , filter(defaultFilter)
55 , wrap()
56 , mipmapSharpness(defaultMipmapSharpness)
57 , graphicsMemorySize(0)
58{
59}
60
61Texture::~Texture()
62{
63 setGraphicsMemorySize(0);
64}
65
66void Texture::initQuad()
67{
68 Quad::Viewport v = {0, 0, (double) width, (double) height};
69 quad.set(new Quad(v, width, height), Acquire::NORETAIN);
70}
71
72void Texture::setGraphicsMemorySize(int64 bytes)
73{
74 totalGraphicsMemory = std::max(totalGraphicsMemory - graphicsMemorySize, (int64) 0);
75
76 bytes = std::max(bytes, (int64) 0);
77 graphicsMemorySize = bytes;
78 totalGraphicsMemory += bytes;
79}
80
81TextureType Texture::getTextureType() const
82{
83 return texType;
84}
85
86PixelFormat Texture::getPixelFormat() const
87{
88 return format;
89}
90
91bool Texture::isReadable() const
92{
93 return readable;
94}
95
96bool Texture::isValidSlice(int slice) const
97{
98 if (slice < 0)
99 return false;
100
101 if (texType == TEXTURE_CUBE)
102 return slice < 6;
103 else if (texType == TEXTURE_VOLUME)
104 return slice < depth;
105 else if (texType == TEXTURE_2D_ARRAY)
106 return slice < layers;
107 else if (slice > 0)
108 return false;
109
110 return true;
111}
112
113void Texture::draw(Graphics *gfx, const Matrix4 &m)
114{
115 draw(gfx, quad, m);
116}
117
118void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform)
119{
120 using namespace vertex;
121
122 if (!readable)
123 throw love::Exception("Textures with non-readable formats cannot be drawn.");
124
125 if (texType == TEXTURE_2D_ARRAY)
126 {
127 drawLayer(gfx, q->getLayer(), q, localTransform);
128 return;
129 }
130
131 const Matrix4 &tm = gfx->getTransform();
132 bool is2D = tm.isAffine2DTransform();
133
134 Graphics::StreamDrawCommand cmd;
135 cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
136 cmd.formats[1] = CommonFormat::STf_RGBAub;
137 cmd.indexMode = TriangleIndexMode::QUADS;
138 cmd.vertexCount = 4;
139 cmd.texture = this;
140
141 Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd);
142
143 Matrix4 t(tm, localTransform);
144
145 if (is2D)
146 t.transformXY((Vector2 *) data.stream[0], q->getVertexPositions(), 4);
147 else
148 t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4);
149
150 const Vector2 *texcoords = q->getVertexTexCoords();
151 vertex::STf_RGBAub *vertexdata = (vertex::STf_RGBAub *) data.stream[1];
152
153 Color32 c = toColor32(gfx->getColor());
154
155 for (int i = 0; i < 4; i++)
156 {
157 vertexdata[i].s = texcoords[i].x;
158 vertexdata[i].t = texcoords[i].y;
159 vertexdata[i].color = c;
160 }
161}
162
163void Texture::drawLayer(Graphics *gfx, int layer, const Matrix4 &m)
164{
165 drawLayer(gfx, layer, quad, m);
166}
167
168void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m)
169{
170 using namespace vertex;
171
172 if (!readable)
173 throw love::Exception("Textures with non-readable formats cannot be drawn.");
174
175 if (texType != TEXTURE_2D_ARRAY)
176 throw love::Exception("drawLayer can only be used with Array Textures!");
177
178 if (layer < 0 || layer >= layers)
179 throw love::Exception("Invalid layer: %d (Texture has %d layers)", layer + 1, layers);
180
181 Color32 c = toColor32(gfx->getColor());
182
183 const Matrix4 &tm = gfx->getTransform();
184 bool is2D = tm.isAffine2DTransform();
185
186 Matrix4 t(tm, m);
187
188 Graphics::StreamDrawCommand cmd;
189 cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
190 cmd.formats[1] = CommonFormat::STPf_RGBAub;
191 cmd.indexMode = TriangleIndexMode::QUADS;
192 cmd.vertexCount = 4;
193 cmd.texture = this;
194 cmd.standardShaderType = Shader::STANDARD_ARRAY;
195
196 Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd);
197
198 if (is2D)
199 t.transformXY((Vector2 *) data.stream[0], q->getVertexPositions(), 4);
200 else
201 t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4);
202
203 const Vector2 *texcoords = q->getVertexTexCoords();
204 vertex::STPf_RGBAub *vertexdata = (vertex::STPf_RGBAub *) data.stream[1];
205
206 for (int i = 0; i < 4; i++)
207 {
208 vertexdata[i].s = texcoords[i].x;
209 vertexdata[i].t = texcoords[i].y;
210 vertexdata[i].p = (float) layer;
211 vertexdata[i].color = c;
212 }
213}
214
215int Texture::getWidth(int mip) const
216{
217 return std::max(width >> mip, 1);
218}
219
220int Texture::getHeight(int mip) const
221{
222 return std::max(height >> mip, 1);
223}
224
225int Texture::getDepth(int mip) const
226{
227 return std::max(depth >> mip, 1);
228}
229
230int Texture::getLayerCount() const
231{
232 return layers;
233}
234
235int Texture::getMipmapCount() const
236{
237 return mipmapCount;
238}
239
240int Texture::getPixelWidth(int mip) const
241{
242 return std::max(pixelWidth >> mip, 1);
243}
244
245int Texture::getPixelHeight(int mip) const
246{
247 return std::max(pixelHeight >> mip, 1);
248}
249
250float Texture::getDPIScale() const
251{
252 return (float) pixelHeight / (float) height;
253}
254
255void Texture::setFilter(const Filter &f)
256{
257 if (!validateFilter(f, getMipmapCount() > 1))
258 {
259 if (f.mipmap != FILTER_NONE && getMipmapCount() == 1)
260 throw love::Exception("Non-mipmapped texture cannot have mipmap filtering.");
261 else
262 throw love::Exception("Invalid texture filter.");
263 }
264
265 Graphics::flushStreamDrawsGlobal();
266
267 filter = f;
268}
269
270const Texture::Filter &Texture::getFilter() const
271{
272 return filter;
273}
274
275const Texture::Wrap &Texture::getWrap() const
276{
277 return wrap;
278}
279
280float Texture::getMipmapSharpness() const
281{
282 return mipmapSharpness;
283}
284
285void Texture::setDepthSampleMode(Optional<CompareMode> mode)
286{
287 if (mode.hasValue && (!readable || !isPixelFormatDepthStencil(format)))
288 throw love::Exception("Only readable depth textures can have a depth sample compare mode.");
289}
290
291Optional<CompareMode> Texture::getDepthSampleMode() const
292{
293 return depthCompareMode;
294}
295
296Quad *Texture::getQuad() const
297{
298 return quad;
299}
300
301bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed)
302{
303 if (!mipmapsAllowed && f.mipmap != FILTER_NONE)
304 return false;
305
306 if (f.mag != FILTER_LINEAR && f.mag != FILTER_NEAREST)
307 return false;
308
309 if (f.min != FILTER_LINEAR && f.min != FILTER_NEAREST)
310 return false;
311
312 if (f.mipmap != FILTER_LINEAR && f.mipmap != FILTER_NEAREST && f.mipmap != FILTER_NONE)
313 return false;
314
315 return true;
316}
317
318int Texture::getTotalMipmapCount(int w, int h)
319{
320 return (int) log2(std::max(w, h)) + 1;
321}
322
323int Texture::getTotalMipmapCount(int w, int h, int d)
324{
325 return (int) log2(std::max(std::max(w, h), d)) + 1;
326}
327
328bool Texture::validateDimensions(bool throwException) const
329{
330 bool success = true;
331
332 auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
333 if (gfx == nullptr)
334 return false;
335
336 const Graphics::Capabilities &caps = gfx->getCapabilities();
337
338 int max2Dsize = (int) caps.limits[Graphics::LIMIT_TEXTURE_SIZE];
339 int max3Dsize = (int) caps.limits[Graphics::LIMIT_VOLUME_TEXTURE_SIZE];
340 int maxcubesize = (int) caps.limits[Graphics::LIMIT_CUBE_TEXTURE_SIZE];
341 int maxlayers = (int) caps.limits[Graphics::LIMIT_TEXTURE_LAYERS];
342
343 int largestdim = 0;
344 const char *largestname = nullptr;
345
346 if ((texType == TEXTURE_2D || texType == TEXTURE_2D_ARRAY) && (pixelWidth > max2Dsize || pixelHeight > max2Dsize))
347 {
348 success = false;
349 largestdim = std::max(pixelWidth, pixelHeight);
350 largestname = pixelWidth > pixelHeight ? "pixel width" : "pixel height";
351 }
352 else if (texType == TEXTURE_2D_ARRAY && layers > maxlayers)
353 {
354 success = false;
355 largestdim = layers;
356 largestname = "array layer count";
357 }
358 else if (texType == TEXTURE_CUBE && (pixelWidth > maxcubesize || pixelWidth != pixelHeight))
359 {
360 success = false;
361 largestdim = std::max(pixelWidth, pixelHeight);
362 largestname = pixelWidth > pixelHeight ? "pixel width" : "pixel height";
363
364 if (throwException && pixelWidth != pixelHeight)
365 throw love::Exception("Cubemap textures must have equal width and height.");
366 }
367 else if (texType == TEXTURE_VOLUME && (pixelWidth > max3Dsize || pixelHeight > max3Dsize || depth > max3Dsize))
368 {
369 success = false;
370 largestdim = std::max(std::max(pixelWidth, pixelHeight), depth);
371 if (largestdim == pixelWidth)
372 largestname = "pixel width";
373 else if (largestdim == pixelHeight)
374 largestname = "pixel height";
375 else
376 largestname = "pixel depth";
377 }
378
379 if (throwException && largestname != nullptr)
380 throw love::Exception("Cannot create texture: %s of %d is too large for this system.", largestname, largestdim);
381
382 return success;
383}
384
385bool Texture::getConstant(const char *in, TextureType &out)
386{
387 return texTypes.find(in, out);
388}
389
390bool Texture::getConstant(TextureType in, const char *&out)
391{
392 return texTypes.find(in, out);
393}
394
395std::vector<std::string> Texture::getConstants(TextureType)
396{
397 return texTypes.getNames();
398}
399
400bool Texture::getConstant(const char *in, FilterMode &out)
401{
402 return filterModes.find(in, out);
403}
404
405bool Texture::getConstant(FilterMode in, const char *&out)
406{
407 return filterModes.find(in, out);
408}
409
410std::vector<std::string> Texture::getConstants(FilterMode)
411{
412 return filterModes.getNames();
413}
414
415bool Texture::getConstant(const char *in, WrapMode &out)
416{
417 return wrapModes.find(in, out);
418}
419
420bool Texture::getConstant(WrapMode in, const char *&out)
421{
422 return wrapModes.find(in, out);
423}
424
425std::vector<std::string> Texture::getConstants(WrapMode)
426{
427 return wrapModes.getNames();
428}
429
430StringMap<TextureType, TEXTURE_MAX_ENUM>::Entry Texture::texTypeEntries[] =
431{
432 { "2d", TEXTURE_2D },
433 { "volume", TEXTURE_VOLUME },
434 { "array", TEXTURE_2D_ARRAY },
435 { "cube", TEXTURE_CUBE },
436};
437
438StringMap<TextureType, TEXTURE_MAX_ENUM> Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries));
439
440StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM>::Entry Texture::filterModeEntries[] =
441{
442 { "linear", FILTER_LINEAR },
443 { "nearest", FILTER_NEAREST },
444 { "none", FILTER_NONE },
445};
446
447StringMap<Texture::FilterMode, Texture::FILTER_MAX_ENUM> Texture::filterModes(Texture::filterModeEntries, sizeof(Texture::filterModeEntries));
448
449StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM>::Entry Texture::wrapModeEntries[] =
450{
451 { "clamp", WRAP_CLAMP },
452 { "clampzero", WRAP_CLAMP_ZERO },
453 { "repeat", WRAP_REPEAT },
454 { "mirroredrepeat", WRAP_MIRRORED_REPEAT },
455};
456
457StringMap<Texture::WrapMode, Texture::WRAP_MAX_ENUM> Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries));
458
459} // graphics
460} // love
461