/** * Copyright (c) 2006-2018 LOVE Development Team * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. **/ // LOVE #include "common/config.h" #include "Texture.h" #include "Graphics.h" // C #include #include namespace love { namespace graphics { love::Type Texture::type("Texture", &Drawable::type); Texture::Filter Texture::defaultFilter; Texture::FilterMode Texture::defaultMipmapFilter = Texture::FILTER_LINEAR; float Texture::defaultMipmapSharpness = 0.0f; int64 Texture::totalGraphicsMemory = 0; Texture::Texture(TextureType texType) : texType(texType) , format(PIXELFORMAT_UNKNOWN) , readable(true) , width(0) , height(0) , depth(1) , layers(1) , mipmapCount(1) , pixelWidth(0) , pixelHeight(0) , filter(defaultFilter) , wrap() , mipmapSharpness(defaultMipmapSharpness) , graphicsMemorySize(0) { } Texture::~Texture() { setGraphicsMemorySize(0); } void Texture::initQuad() { Quad::Viewport v = {0, 0, (double) width, (double) height}; quad.set(new Quad(v, width, height), Acquire::NORETAIN); } void Texture::setGraphicsMemorySize(int64 bytes) { totalGraphicsMemory = std::max(totalGraphicsMemory - graphicsMemorySize, (int64) 0); bytes = std::max(bytes, (int64) 0); graphicsMemorySize = bytes; totalGraphicsMemory += bytes; } TextureType Texture::getTextureType() const { return texType; } PixelFormat Texture::getPixelFormat() const { return format; } bool Texture::isReadable() const { return readable; } bool Texture::isValidSlice(int slice) const { if (slice < 0) return false; if (texType == TEXTURE_CUBE) return slice < 6; else if (texType == TEXTURE_VOLUME) return slice < depth; else if (texType == TEXTURE_2D_ARRAY) return slice < layers; else if (slice > 0) return false; return true; } void Texture::draw(Graphics *gfx, const Matrix4 &m) { draw(gfx, quad, m); } void Texture::draw(Graphics *gfx, Quad *q, const Matrix4 &localTransform) { using namespace vertex; if (!readable) throw love::Exception("Textures with non-readable formats cannot be drawn."); if (texType == TEXTURE_2D_ARRAY) { drawLayer(gfx, q->getLayer(), q, localTransform); return; } const Matrix4 &tm = gfx->getTransform(); bool is2D = tm.isAffine2DTransform(); Graphics::StreamDrawCommand cmd; cmd.formats[0] = vertex::getSinglePositionFormat(is2D); cmd.formats[1] = CommonFormat::STf_RGBAub; cmd.indexMode = TriangleIndexMode::QUADS; cmd.vertexCount = 4; cmd.texture = this; Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd); Matrix4 t(tm, localTransform); if (is2D) t.transformXY((Vector2 *) data.stream[0], q->getVertexPositions(), 4); else t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4); const Vector2 *texcoords = q->getVertexTexCoords(); vertex::STf_RGBAub *vertexdata = (vertex::STf_RGBAub *) data.stream[1]; Color c = toColor(gfx->getColor()); for (int i = 0; i < 4; i++) { vertexdata[i].s = texcoords[i].x; vertexdata[i].t = texcoords[i].y; vertexdata[i].color = c; } } void Texture::drawLayer(Graphics *gfx, int layer, const Matrix4 &m) { drawLayer(gfx, layer, quad, m); } void Texture::drawLayer(Graphics *gfx, int layer, Quad *q, const Matrix4 &m) { using namespace vertex; if (!readable) throw love::Exception("Textures with non-readable formats cannot be drawn."); if (texType != TEXTURE_2D_ARRAY) throw love::Exception("drawLayer can only be used with Array Textures!"); if (layer < 0 || layer >= layers) throw love::Exception("Invalid layer: %d (Texture has %d layers)", layer + 1, layers); Color c = toColor(gfx->getColor()); const Matrix4 &tm = gfx->getTransform(); bool is2D = tm.isAffine2DTransform(); Matrix4 t(tm, m); Graphics::StreamDrawCommand cmd; cmd.formats[0] = vertex::getSinglePositionFormat(is2D); cmd.formats[1] = CommonFormat::STPf_RGBAub; cmd.indexMode = TriangleIndexMode::QUADS; cmd.vertexCount = 4; cmd.texture = this; cmd.standardShaderType = Shader::STANDARD_ARRAY; Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd); if (is2D) t.transformXY((Vector2 *) data.stream[0], q->getVertexPositions(), 4); else t.transformXY0((Vector3 *) data.stream[0], q->getVertexPositions(), 4); const Vector2 *texcoords = q->getVertexTexCoords(); vertex::STPf_RGBAub *vertexdata = (vertex::STPf_RGBAub *) data.stream[1]; for (int i = 0; i < 4; i++) { vertexdata[i].s = texcoords[i].x; vertexdata[i].t = texcoords[i].y; vertexdata[i].p = (float) layer; vertexdata[i].color = c; } } int Texture::getWidth(int mip) const { return std::max(width >> mip, 1); } int Texture::getHeight(int mip) const { return std::max(height >> mip, 1); } int Texture::getDepth(int mip) const { return std::max(depth >> mip, 1); } int Texture::getLayerCount() const { return layers; } int Texture::getMipmapCount() const { return mipmapCount; } int Texture::getPixelWidth(int mip) const { return std::max(pixelWidth >> mip, 1); } int Texture::getPixelHeight(int mip) const { return std::max(pixelHeight >> mip, 1); } float Texture::getDPIScale() const { return (float) pixelHeight / (float) height; } void Texture::setFilter(const Filter &f) { if (!validateFilter(f, getMipmapCount() > 1)) { if (f.mipmap != FILTER_NONE && getMipmapCount() == 1) throw love::Exception("Non-mipmapped texture cannot have mipmap filtering."); else throw love::Exception("Invalid texture filter."); } Graphics::flushStreamDrawsGlobal(); filter = f; } const Texture::Filter &Texture::getFilter() const { return filter; } const Texture::Wrap &Texture::getWrap() const { return wrap; } float Texture::getMipmapSharpness() const { return mipmapSharpness; } void Texture::setDepthSampleMode(Optional mode) { if (mode.hasValue && (!readable || !isPixelFormatDepthStencil(format))) throw love::Exception("Only readable depth textures can have a depth sample compare mode."); } Optional Texture::getDepthSampleMode() const { return depthCompareMode; } Quad *Texture::getQuad() const { return quad; } bool Texture::validateFilter(const Filter &f, bool mipmapsAllowed) { if (!mipmapsAllowed && f.mipmap != FILTER_NONE) return false; if (f.mag != FILTER_LINEAR && f.mag != FILTER_NEAREST) return false; if (f.min != FILTER_LINEAR && f.min != FILTER_NEAREST) return false; if (f.mipmap != FILTER_LINEAR && f.mipmap != FILTER_NEAREST && f.mipmap != FILTER_NONE) return false; return true; } int Texture::getTotalMipmapCount(int w, int h) { return (int) log2(std::max(w, h)) + 1; } int Texture::getTotalMipmapCount(int w, int h, int d) { return (int) log2(std::max(std::max(w, h), d)) + 1; } bool Texture::validateDimensions(bool throwException) const { bool success = true; auto gfx = Module::getInstance(Module::M_GRAPHICS); if (gfx == nullptr) return false; const Graphics::Capabilities &caps = gfx->getCapabilities(); int max2Dsize = (int) caps.limits[Graphics::LIMIT_TEXTURE_SIZE]; int max3Dsize = (int) caps.limits[Graphics::LIMIT_VOLUME_TEXTURE_SIZE]; int maxcubesize = (int) caps.limits[Graphics::LIMIT_CUBE_TEXTURE_SIZE]; int maxlayers = (int) caps.limits[Graphics::LIMIT_TEXTURE_LAYERS]; int largestdim = 0; const char *largestname = nullptr; if ((texType == TEXTURE_2D || texType == TEXTURE_2D_ARRAY) && (pixelWidth > max2Dsize || pixelHeight > max2Dsize)) { success = false; largestdim = std::max(pixelWidth, pixelHeight); largestname = pixelWidth > pixelHeight ? "pixel width" : "pixel height"; } else if (texType == TEXTURE_2D_ARRAY && layers > maxlayers) { success = false; largestdim = layers; largestname = "array layer count"; } else if (texType == TEXTURE_CUBE && (pixelWidth > maxcubesize || pixelWidth != pixelHeight)) { success = false; largestdim = std::max(pixelWidth, pixelHeight); largestname = pixelWidth > pixelHeight ? "pixel width" : "pixel height"; if (throwException && pixelWidth != pixelHeight) throw love::Exception("Cubemap textures must have equal width and height."); } else if (texType == TEXTURE_VOLUME && (pixelWidth > max3Dsize || pixelHeight > max3Dsize || depth > max3Dsize)) { success = false; largestdim = std::max(std::max(pixelWidth, pixelHeight), depth); if (largestdim == pixelWidth) largestname = "pixel width"; else if (largestdim == pixelHeight) largestname = "pixel height"; else largestname = "pixel depth"; } if (throwException && largestname != nullptr) throw love::Exception("Cannot create texture: %s of %d is too large for this system.", largestname, largestdim); return success; } bool Texture::getConstant(const char *in, TextureType &out) { return texTypes.find(in, out); } bool Texture::getConstant(TextureType in, const char *&out) { return texTypes.find(in, out); } std::vector Texture::getConstants(TextureType) { return texTypes.getNames(); } bool Texture::getConstant(const char *in, FilterMode &out) { return filterModes.find(in, out); } bool Texture::getConstant(FilterMode in, const char *&out) { return filterModes.find(in, out); } std::vector Texture::getConstants(FilterMode) { return filterModes.getNames(); } bool Texture::getConstant(const char *in, WrapMode &out) { return wrapModes.find(in, out); } bool Texture::getConstant(WrapMode in, const char *&out) { return wrapModes.find(in, out); } std::vector Texture::getConstants(WrapMode) { return wrapModes.getNames(); } StringMap::Entry Texture::texTypeEntries[] = { { "2d", TEXTURE_2D }, { "volume", TEXTURE_VOLUME }, { "array", TEXTURE_2D_ARRAY }, { "cube", TEXTURE_CUBE }, }; StringMap Texture::texTypes(Texture::texTypeEntries, sizeof(Texture::texTypeEntries)); StringMap::Entry Texture::filterModeEntries[] = { { "linear", FILTER_LINEAR }, { "nearest", FILTER_NEAREST }, { "none", FILTER_NONE }, }; StringMap Texture::filterModes(Texture::filterModeEntries, sizeof(Texture::filterModeEntries)); StringMap::Entry Texture::wrapModeEntries[] = { { "clamp", WRAP_CLAMP }, { "clampzero", WRAP_CLAMP_ZERO }, { "repeat", WRAP_REPEAT }, { "mirroredrepeat", WRAP_MIRRORED_REPEAT }, }; StringMap Texture::wrapModes(Texture::wrapModeEntries, sizeof(Texture::wrapModeEntries)); } // graphics } // love