Browse Source

graphics: handle default textures in high level code.

Previously each backend had to set up default textures with its own code, which needs some care for it to be robust.
Sasha Szpakowski 1 year ago
parent
commit
8872497561

+ 93 - 6
src/modules/graphics/Graphics.cpp

@@ -195,6 +195,7 @@ Graphics::Graphics()
 	, quadIndexBuffer(nullptr)
 	, fanIndexBuffer(nullptr)
 	, capabilities()
+	, defaultTextures()
 	, cachedShaderStages()
 {
 	transformStack.reserve(16);
@@ -217,6 +218,8 @@ Graphics::~Graphics()
 	if (fanIndexBuffer != nullptr)
 		fanIndexBuffer->release();
 
+	releaseDefaultResources();
+
 	// Clean up standard shaders before the active shader. If we do it after,
 	// the active shader may try to activate a standard shader when deactivating
 	// itself, which will cause problems since it calls Graphics methods in the
@@ -530,6 +533,90 @@ bool Graphics::validateShader(bool gles, const std::vector<std::string> &stagess
 	return Shader::validate(stages, err);
 }
 
+Texture *Graphics::getDefaultTexture(TextureType type, DataBaseType dataType)
+{
+	Texture *tex = defaultTextures[type][dataType];
+	if (tex != nullptr)
+		return tex;
+
+	Texture::Settings settings;
+	settings.type = type;
+
+	switch (dataType)
+	{
+	case DATA_BASETYPE_INT:
+		settings.format = PIXELFORMAT_RGBA8_INT;
+		break;
+	case DATA_BASETYPE_UINT:
+		settings.format = PIXELFORMAT_RGBA8_UINT;
+		break;
+	case DATA_BASETYPE_FLOAT:
+	default:
+		settings.format = PIXELFORMAT_RGBA8_UNORM;
+		break;
+	}
+
+	std::string name = "default_";
+
+	const char *tname = "unknown";
+	Texture::getConstant(type, tname);
+	name += tname;
+
+	const char *formatname = "unknown";
+	love::getConstant(settings.format, formatname);
+	name += std::string("_") + formatname;
+
+	settings.debugName = name;
+
+	tex = newTexture(settings);
+
+	SamplerState s;
+	s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
+	s.wrapU = s.wrapV = s.wrapW = SamplerState::WRAP_CLAMP;
+	tex->setSamplerState(s);
+
+	uint8 pixel[] = {255, 255, 255, 255};
+	if (isPixelFormatInteger(settings.format))
+		pixel[0] = pixel[1] = pixel[2] = pixel[3] = 1;
+
+	for (int slice = 0; slice < (type == TEXTURE_CUBE ? 6 : 1); slice++)
+		tex->replacePixels(pixel, sizeof(pixel), slice, 0, {0, 0, 1, 1}, false);
+
+	defaultTextures[type][dataType] = tex;
+
+	return tex;
+}
+
+void Graphics::releaseDefaultResources()
+{
+	for (int type = 0; type < TEXTURE_MAX_ENUM; type++)
+	{
+		for (int dataType = 0; dataType < DATA_BASETYPE_MAX_ENUM; dataType++)
+		{
+			if (defaultTextures[type][dataType])
+				defaultTextures[type][dataType]->release();
+			defaultTextures[type][dataType] = nullptr;
+		}
+	}
+}
+
+Texture *Graphics::getTextureOrDefaultForActiveShader(Texture *tex)
+{
+	if (tex != nullptr)
+		return tex;
+
+	Shader *shader = Shader::current;
+
+	if (shader != nullptr)
+	{
+		auto texinfo = shader->getMainTextureInfo();
+		if (texinfo != nullptr && texinfo->textureType != TEXTURE_MAX_ENUM)
+			return getDefaultTexture(texinfo->textureType, texinfo->dataBaseType);
+	}
+
+	return getDefaultTexture(TEXTURE_2D, DATA_BASETYPE_FLOAT);
+}
+
 int Graphics::getWidth() const
 {
 	return width;
@@ -1825,7 +1912,7 @@ void Graphics::flushBatchedDraws()
 		cmd.indexCount = sbstate.indexCount;
 		cmd.indexType = INDEX_UINT16;
 		cmd.indexBufferOffset = sbstate.indexBuffer->unmap(usedsizes[2]);
-		cmd.texture = sbstate.texture;
+		cmd.texture = getTextureOrDefaultForActiveShader(sbstate.texture);
 		draw(cmd);
 
 		sbstate.indexBufferMap = StreamBuffer::MapInfo();
@@ -1836,7 +1923,7 @@ void Graphics::flushBatchedDraws()
 		cmd.primitiveType = sbstate.primitiveMode;
 		cmd.vertexStart = 0;
 		cmd.vertexCount = sbstate.vertexCount;
-		cmd.texture = sbstate.texture;
+		cmd.texture = getTextureOrDefaultForActiveShader(sbstate.texture);
 		draw(cmd);
 	}
 
@@ -1933,7 +2020,7 @@ void Graphics::drawFromShader(PrimitiveType primtype, int vertexcount, int insta
 	cmd.primitiveType = primtype;
 	cmd.vertexCount = vertexcount;
 	cmd.instanceCount = std::max(1, instancecount);
-	cmd.texture = maintexture;
+	cmd.texture = getTextureOrDefaultForActiveShader(maintexture);
 
 	draw(cmd);
 }
@@ -1974,7 +2061,7 @@ void Graphics::drawFromShader(Buffer *indexbuffer, int indexcount, int instancec
 	cmd.indexType = getIndexDataType(indexbuffer->getDataMember(0).decl.format);
 	cmd.indexBufferOffset = startindex * getIndexDataSize(cmd.indexType);
 
-	cmd.texture = maintexture;
+	cmd.texture = getTextureOrDefaultForActiveShader(maintexture);
 
 	draw(cmd);
 }
@@ -2001,7 +2088,7 @@ void Graphics::drawFromShaderIndirect(PrimitiveType primtype, Buffer *indirectar
 	cmd.primitiveType = primtype;
 	cmd.indirectBuffer = indirectargs;
 	cmd.indirectBufferOffset = argsindex * indirectargs->getArrayStride();
-	cmd.texture = maintexture;
+	cmd.texture = getTextureOrDefaultForActiveShader(maintexture);
 
 	draw(cmd);
 }
@@ -2029,7 +2116,7 @@ void Graphics::drawFromShaderIndirect(Buffer *indexbuffer, Buffer *indirectargs,
 	cmd.indexType = getIndexDataType(indexbuffer->getDataMember(0).decl.format);
 	cmd.indirectBuffer = indirectargs;
 	cmd.indexBufferOffset = argsindex * indirectargs->getArrayStride();
-	cmd.texture = maintexture;
+	cmd.texture = getTextureOrDefaultForActiveShader(maintexture);
 
 	draw(cmd);
 }

+ 7 - 0
src/modules/graphics/Graphics.h

@@ -487,6 +487,9 @@ public:
 
 	bool validateShader(bool gles, const std::vector<std::string> &stages, const Shader::CompileOptions &options, std::string &err);
 
+	Texture *getDefaultTexture(TextureType type, DataBaseType dataType);
+	Texture *getTextureOrDefaultForActiveShader(Texture *tex);
+
 	/**
 	 * Resets the current color, background color, line style, and so forth.
 	 **/
@@ -1039,6 +1042,8 @@ protected:
 
 	void updatePendingReadbacks();
 
+	void releaseDefaultResources();
+
 	void restoreState(const DisplayState &s);
 	void restoreStateChecked(const DisplayState &s);
 
@@ -1094,6 +1099,8 @@ private:
 	void checkSetDefaultFont();
 	int calculateEllipsePoints(float rx, float ry) const;
 
+	Texture *defaultTextures[TEXTURE_MAX_ENUM][DATA_BASETYPE_MAX_ENUM];
+
 	std::vector<uint8> scratchBuffer;
 
 	std::unordered_map<std::string, ShaderStage *> cachedShaderStages[SHADERSTAGE_MAX_ENUM];

+ 2 - 2
src/modules/graphics/Mesh.cpp

@@ -667,7 +667,7 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff
 		cmd.primitiveType = primitiveType;
 		cmd.indexType = indexDataType;
 		cmd.instanceCount = instancecount;
-		cmd.texture = texture;
+		cmd.texture = gfx->getTextureOrDefaultForActiveShader(texture);
 		cmd.cullMode = gfx->getMeshCullMode();
 
 		cmd.indexBufferOffset = r.getOffset() * indexbuffer->getArrayStride();
@@ -691,7 +691,7 @@ void Mesh::drawInternal(Graphics *gfx, const Matrix4 &m, int instancecount, Buff
 		cmd.vertexStart = (int) r.getOffset();
 		cmd.vertexCount = (int) r.getSize();
 		cmd.instanceCount = instancecount;
-		cmd.texture = texture;
+		cmd.texture = gfx->getTextureOrDefaultForActiveShader(texture);
 		cmd.cullMode = gfx->getMeshCullMode();
 
 		cmd.indirectBuffer = indirectargs;

+ 2 - 1
src/modules/graphics/ParticleSystem.cpp

@@ -1086,7 +1086,8 @@ void ParticleSystem::draw(Graphics *gfx, const Matrix4 &m)
 	BufferBindings vertexbuffers;
 	vertexbuffers.set(0, buffer, 0);
 
-	gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, texture);
+	Texture *tex = gfx->getTextureOrDefaultForActiveShader(texture);
+	gfx->drawQuads(0, pCount, vertexAttributes, vertexbuffers, tex);
 }
 
 bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)

+ 4 - 1
src/modules/graphics/SpriteBatch.cpp

@@ -395,7 +395,10 @@ void SpriteBatch::draw(Graphics *gfx, const Matrix4 &m)
 	count = std::min(count, next - start);
 
 	if (count > 0)
-		gfx->drawQuads(start, count, attributes, buffers, texture);
+	{
+		Texture *tex = gfx->getTextureOrDefaultForActiveShader(texture);
+		gfx->drawQuads(start, count, attributes, buffers, tex);
+	}
 }
 
 } // graphics

+ 4 - 1
src/modules/graphics/TextBatch.cpp

@@ -290,7 +290,10 @@ void TextBatch::draw(Graphics *gfx, const Matrix4 &m)
 	Graphics::TempTransform transform(gfx, m);
 
 	for (const Font::DrawCommand &cmd : drawCommands)
-		gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
+	{
+		Texture *tex = gfx->getTextureOrDefaultForActiveShader(cmd.texture);
+		gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, tex);
+	}
 }
 
 } // graphics

+ 0 - 3
src/modules/graphics/metal/Graphics.h

@@ -139,7 +139,6 @@ public:
 
 	StreamBuffer *getUniformBuffer() const { return uniformBuffer; }
 	Buffer *getDefaultAttributesBuffer() const { return defaultAttributesBuffer; }
-	Texture *getDefaultTexture(TextureType textype) const { return defaultTextures[textype]; }
 
 	int getClosestMSAASamples(int requestedsamples);
 
@@ -247,8 +246,6 @@ private:
 
 	Buffer *defaultAttributesBuffer;
 
-	Texture *defaultTextures[TEXTURE_MAX_ENUM];
-
 	std::map<uint64, void *> cachedSamplers;
 	std::unordered_map<uint64, void *> cachedDepthStencilStates;
 

+ 0 - 22
src/modules/graphics/metal/Graphics.mm

@@ -281,7 +281,6 @@ Graphics::Graphics()
 	, renderBindings()
 	, uniformBufferOffset(0)
 	, defaultAttributesBuffer(nullptr)
-	, defaultTextures()
 	, families()
 { @autoreleasepool {
 	if (@available(macOS 10.15, iOS 13.0, *))
@@ -346,17 +345,6 @@ Graphics::Graphics()
 		defaultAttributesBuffer = newBuffer(attribsettings, dataformat, &defaults, sizeof(DefaultVertexAttributes), 0);
 	}
 
-	uint8 defaultpixel[] = {255, 255, 255, 255};
-	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
-	{
-		Texture::Settings settings;
-		settings.type = (TextureType) i;
-		settings.format = PIXELFORMAT_RGBA8_UNORM;
-		defaultTextures[i] = newTexture(settings);
-		Rect r = {0, 0, 1, 1};
-		defaultTextures[i]->replacePixels(defaultpixel, sizeof(defaultpixel), 0, 0, r, false);
-	}
-
 	if (batchedDrawState.vb[0] == nullptr)
 	{
 		// Initial sizes that should be good enough for most cases. It will
@@ -426,9 +414,6 @@ Graphics::~Graphics()
 	commandQueue = nil;
 	device = nil;
 
-	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
-		defaultTextures[i]->release();
-
 	for (auto &kvp : cachedSamplers)
 		CFBridgingRelease(kvp.second);
 
@@ -1144,13 +1129,6 @@ void Graphics::applyShaderUniforms(id<MTLRenderCommandEncoder> renderEncoder, lo
 
 		if (b.isMainTexture)
 		{
-			if (maintex == nullptr)
-			{
-				auto texinfo = shader->getMainTextureInfo();
-				if (texinfo != nullptr && texinfo->textureType != TEXTURE_MAX_ENUM)
-					maintex = defaultTextures[texinfo->textureType];
-			}
-
 			texture = getMTLTexture(maintex);
 			samplertex = maintex;
 		}

+ 10 - 3
src/modules/graphics/metal/Shader.mm

@@ -524,7 +524,7 @@ void Shader::addImage(const spirv_cross::CompilerMSL &msl, const spirv_cross::Re
 
 	if (u.baseType == UNIFORM_SAMPLER)
 	{
-		auto tex = Graphics::getInstance()->getDefaultTexture(u.textureType);
+		auto tex = Graphics::getInstance()->getDefaultTexture(u.textureType, u.dataBaseType);
 		for (int i = 0; i < u.count; i++)
 		{
 			tex->retain();
@@ -538,8 +538,15 @@ void Shader::addImage(const spirv_cross::CompilerMSL &msl, const spirv_cross::Re
 	}
 	else if (u.baseType == UNIFORM_STORAGETEXTURE)
 	{
+		Texture *tex = nullptr;
+		if ((u.access & ACCESS_WRITE) == 0)
+			tex = Graphics::getInstance()->getDefaultTexture(u.textureType, u.dataBaseType);
 		for (int i = 0; i < u.count; i++)
-			u.textures[i] = nullptr;
+		{
+			if (tex)
+				tex->retain();
+			u.textures[i] = tex;
+		}
 	}
 
 	uniforms[u.name] = u;
@@ -983,7 +990,7 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 		else
 		{
 			auto gfx = Graphics::getInstance();
-			tex = gfx->getDefaultTexture(info->textureType);
+			tex = gfx->getDefaultTexture(info->textureType, info->dataBaseType);
 		}
 
 		tex->retain();

+ 4 - 1
src/modules/graphics/opengl/Graphics.cpp

@@ -1765,7 +1765,10 @@ uint32 Graphics::computePixelFormatUsage(PixelFormat format, bool readable)
 		// Make sure at least something is bound to a color attachment. I believe
 		// this is required on ES2 but I'm not positive.
 		if (isPixelFormatDepthStencil(format))
-			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, gl.getDefaultTexture(TEXTURE_2D, DATA_BASETYPE_FLOAT), 0, 0, 0);
+		{
+			love::graphics::Texture *tex = getDefaultTexture(TEXTURE_2D, DATA_BASETYPE_FLOAT);
+			gl.framebufferTexture(GL_COLOR_ATTACHMENT0, TEXTURE_2D, (GLuint) tex->getHandle(), 0, 0, 0);
+		}
 
 		if (readable)
 		{

+ 5 - 109
src/modules/graphics/opengl/OpenGL.cpp

@@ -323,8 +323,6 @@ void OpenGL::setupContext()
 	setStencilWriteMask(state.stencilWriteMask);
 	setColorWriteMask(state.colorWriteMask);
 
-	createDefaultTexture();
-
 	contextInitialized = true;
 
 #ifdef LOVE_ANDROID
@@ -341,18 +339,6 @@ void OpenGL::deInitContext()
 	if (!contextInitialized)
 		return;
 
-	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
-	{
-		for (int datatype = DATA_BASETYPE_FLOAT; datatype <= DATA_BASETYPE_UINT; datatype++)
-		{
-			if (state.defaultTexture[i][datatype] != 0)
-			{
-				gl.deleteTexture(state.defaultTexture[i][datatype]);
-				state.defaultTexture[i][datatype] = 0;
-			}
-		}
-	}
-
 	contextInitialized = false;
 }
 
@@ -604,71 +590,6 @@ void OpenGL::initMaxValues()
 		maxLODBias = 0.0f;
 }
 
-void OpenGL::createDefaultTexture()
-{
-	// Set the 'default' texture as a repeating white pixel. Otherwise, texture
-	// calls inside a shader would return black when drawing graphics primitives
-	// which would create the need to use different "passthrough" shaders for
-	// untextured primitives vs images.
-	const GLubyte pix[] = {255, 255, 255, 255};
-	const GLubyte intpix[] = {1, 1, 1, 1};
-
-	SamplerState s;
-	s.minFilter = s.magFilter = SamplerState::FILTER_NEAREST;
-	s.wrapU = s.wrapV = s.wrapW = SamplerState::WRAP_CLAMP;
-
-	for (int i = 0; i < TEXTURE_MAX_ENUM; i++)
-	{
-		for (int datatype = (int)DATA_BASETYPE_FLOAT; datatype <= (int)DATA_BASETYPE_UINT; datatype++)
-		{
-			state.defaultTexture[i][datatype] = 0;
-
-			TextureType type = (TextureType) i;
-
-			if (!isTextureTypeSupported(type))
-				continue;
-
-			if (datatype != DATA_BASETYPE_FLOAT && !(GLAD_VERSION_3_0 || GLAD_ES_VERSION_3_0))
-				continue;
-
-			GLuint curtexture = state.boundTextures[type][0];
-
-			glGenTextures(1, &state.defaultTexture[type][datatype]);
-			bindTextureToUnit(type, state.defaultTexture[type][datatype], 0, false);
-
-			setSamplerState(type, s);
-
-			PixelFormat format = PIXELFORMAT_RGBA8_UNORM;
-			if (datatype == DATA_BASETYPE_INT)
-				format = PIXELFORMAT_RGBA8_INT;
-			else if (datatype == DATA_BASETYPE_UINT)
-				format = PIXELFORMAT_RGBA8_UINT;
-
-			const GLubyte *p = datatype == DATA_BASETYPE_FLOAT ? pix : intpix;
-
-			rawTexStorage(type, 1, format, 1, 1);
-
-			TextureFormat fmt = convertPixelFormat(format, false);
-			int slices = type == TEXTURE_CUBE ? 6 : 1;
-
-			for (int slice = 0; slice < slices; slice++)
-			{
-				GLenum gltarget = getGLTextureType(type);
-
-				if (type == TEXTURE_CUBE)
-					gltarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X + slice;
-
-				if (type == TEXTURE_2D || type == TEXTURE_CUBE)
-					glTexSubImage2D(gltarget, 0, 0, 0, 1, 1, fmt.externalformat, fmt.type, p);
-				else if (type == TEXTURE_2D_ARRAY || type == TEXTURE_VOLUME)
-					glTexSubImage3D(gltarget, 0, 0, 0, slice, 1, 1, 1, fmt.externalformat, fmt.type, p);
-			}
-
-			bindTextureToUnit(type, curtexture, 0, false);
-		}
-	}
-}
-
 void OpenGL::prepareDraw(love::graphics::Graphics *gfx)
 {
 	TempDebugGroup debuggroup("Prepare OpenGL draw");
@@ -702,6 +623,7 @@ GLenum OpenGL::getGLBufferType(BufferUsage usage)
 		case BUFFERUSAGE_VERTEX: return GL_ARRAY_BUFFER;
 		case BUFFERUSAGE_INDEX: return GL_ELEMENT_ARRAY_BUFFER;
 		case BUFFERUSAGE_TEXEL: return GL_TEXTURE_BUFFER;
+		case BUFFERUSAGE_UNIFORM: return GL_UNIFORM_BUFFER;
 		case BUFFERUSAGE_SHADER_STORAGE: return GL_SHADER_STORAGE_BUFFER;
 		case BUFFERUSAGE_INDIRECT_ARGUMENTS: return GL_DRAW_INDIRECT_BUFFER;
 		case BUFFERUSAGE_MAX_ENUM: return GL_ZERO;
@@ -1191,11 +1113,6 @@ GLuint OpenGL::getDefaultFBO() const
 #endif
 }
 
-GLuint OpenGL::getDefaultTexture(TextureType type, DataBaseType datatype) const
-{
-	return state.defaultTexture[type][datatype];
-}
-
 void OpenGL::setTextureUnit(int textureunit)
 {
 	if (textureunit != state.curTextureUnit)
@@ -1234,31 +1151,8 @@ void OpenGL::bindBufferTextureToUnit(GLuint texture, int textureunit, bool resto
 
 void OpenGL::bindTextureToUnit(Texture *texture, int textureunit, bool restoreprev, bool bindforedit)
 {
-	TextureType textype = TEXTURE_2D;
-	GLuint handle = 0;
-
-	if (texture != nullptr)
-	{
-		textype = texture->getTextureType();
-		handle = (GLuint) texture->getHandle();
-	}
-	else
-	{
-		DataBaseType datatype = DATA_BASETYPE_FLOAT;
-
-		if (textureunit == 0 && Shader::current != nullptr)
-		{
-			const Shader::UniformInfo *info = Shader::current->getMainTextureInfo();
-			if (info != nullptr)
-			{
-				textype = info->textureType;
-				datatype = info->dataBaseType;
-			}
-		}
-
-		handle = getDefaultTexture(textype, datatype);
-	}
-
+	TextureType textype = texture->getTextureType();
+	GLuint handle = (GLuint) texture->getHandle();
 	bindTextureToUnit(textype, handle, textureunit, restoreprev, bindforedit);
 }
 
@@ -1537,6 +1431,8 @@ bool OpenGL::isBufferUsageSupported(BufferUsage usage) const
 		return true;
 	case BUFFERUSAGE_TEXEL:
 		return GLAD_VERSION_3_1 || GLAD_ES_VERSION_3_2;
+	case BUFFERUSAGE_UNIFORM:
+		return GLAD_VERSION_3_1 || GLAD_ES_VERSION_3_0;
 	case BUFFERUSAGE_SHADER_STORAGE:
 		return (GLAD_VERSION_4_3 && isCoreProfile()) || GLAD_ES_VERSION_3_1;
 	case BUFFERUSAGE_INDIRECT_ARGUMENTS:

+ 0 - 7
src/modules/graphics/opengl/OpenGL.h

@@ -330,11 +330,6 @@ public:
 	 **/
 	GLuint getDefaultFBO() const;
 
-	/**
-	 * Gets the ID for love's default texture (used for "untextured" primitives.)
-	 **/
-	GLuint getDefaultTexture(TextureType type, DataBaseType datatype) const;
-
 	/**
 	 * Gets the texture ID for love's default texel buffer.
 	 **/
@@ -492,7 +487,6 @@ private:
 	void initVendor();
 	void initOpenGLFunctions();
 	void initMaxValues();
-	void createDefaultTexture();
 
 	bool contextInitialized;
 
@@ -550,7 +544,6 @@ private:
 
 		GLuint boundFramebuffers[2];
 
-		GLuint defaultTexture[TEXTURE_MAX_ENUM][DATA_BASETYPE_MAX_ENUM];
 		GLuint defaultTexelBuffer;
 		GLuint defaultStorageBuffer;
 

+ 35 - 16
src/modules/graphics/opengl/Shader.cpp

@@ -110,6 +110,8 @@ void Shader::mapActiveUniforms()
 	std::map<std::string, UniformInfo> olduniforms = uniforms;
 	uniforms.clear();
 
+	auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
+
 	for (int uindex = 0; uindex < numuniforms; uindex++)
 	{
 		GLsizei namelen = 0;
@@ -140,7 +142,7 @@ void Shader::mapActiveUniforms()
 			continue;
 
 		if (!fillUniformReflectionData(u))
-			continue;;
+			continue;
 
 		if ((u.baseType == UNIFORM_SAMPLER && builtin != BUILTIN_TEXTURE_MAIN) || u.baseType == UNIFORM_TEXELBUFFER)
 		{
@@ -156,7 +158,7 @@ void Shader::mapActiveUniforms()
 			else
 			{
 				unit.isTexelBuffer = false;
-				unit.texture = gl.getDefaultTexture(u.textureType, u.dataBaseType);
+				unit.texture = 0; // Handled below.
 			}
 
 			for (int i = 0; i < u.count; i++)
@@ -165,7 +167,7 @@ void Shader::mapActiveUniforms()
 		else if (u.baseType == UNIFORM_STORAGETEXTURE)
 		{
 			StorageTextureBinding binding = {};
-			binding.gltexture = gl.getDefaultTexture(u.textureType, u.dataBaseType);
+			binding.gltexture = 0; // Handled below.
 			binding.type = u.textureType;
 
 			if ((u.access & (ACCESS_READ | ACCESS_WRITE)) != 0)
@@ -247,7 +249,13 @@ void Shader::mapActiveUniforms()
 					else
 					{
 						u.textures = new love::graphics::Texture*[u.count];
-						memset(u.textures, 0, sizeof(Texture *) * u.count);
+
+						auto *tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
+						for (int i = 0; i < u.count; i++)
+						{
+							tex->retain();
+							u.textures[i] = tex;
+						}
 					}
 				}
 				else if (u.baseType == UNIFORM_STORAGETEXTURE)
@@ -259,7 +267,20 @@ void Shader::mapActiveUniforms()
 					glUniform1iv(u.location, u.count, u.ints);
 
 					u.textures = new love::graphics::Texture*[u.count];
-					memset(u.textures, 0, sizeof(Texture *) * u.count);
+
+					if ((u.access & ACCESS_WRITE) != 0)
+					{
+						memset(u.textures, 0, sizeof(Texture *) * u.count);
+					}
+					else
+					{
+						auto *tex = gfx->getDefaultTexture(u.textureType, u.dataBaseType);
+						for (int i = 0; i < u.count; i++)
+						{
+							tex->retain();
+							u.textures[i] = tex;
+						}
+					}
 				}
 			}
 
@@ -777,9 +798,15 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 		{
 			if (!validateTexture(info, tex, internalUpdate))
 				continue;
-			tex->retain();
+		}
+		else
+		{
+			auto gfx = Module::getInstance<love::graphics::Graphics>(Module::M_GRAPHICS);
+			tex = gfx->getDefaultTexture(info->textureType, info->dataBaseType);
 		}
 
+		tex->retain();
+
 		if (info->textures[i] != nullptr)
 			info->textures[i]->release();
 
@@ -787,11 +814,7 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 
 		if (isstoragetex)
 		{
-			GLuint gltex = 0;
-			if (tex != nullptr)
-				gltex = (GLuint) tex->getHandle();
-			else
-				gltex = gl.getDefaultTexture(info->textureType, info->dataBaseType);
+			GLuint gltex = (GLuint) tex->getHandle();
 
 			int bindingindex = info->ints[i];
 			auto &binding = storageTextureBindings[bindingindex];
@@ -804,11 +827,7 @@ void Shader::sendTextures(const UniformInfo *info, love::graphics::Texture **tex
 		}
 		else
 		{
-			GLuint gltex = 0;
-			if (textures[i] != nullptr)
-				gltex = (GLuint) tex->getHandle();
-			else
-				gltex = gl.getDefaultTexture(info->textureType, info->dataBaseType);
+			GLuint gltex = (GLuint) tex->getHandle();
 
 			int texunit = info->ints[i];
 

+ 1 - 15
src/modules/graphics/vulkan/Graphics.cpp

@@ -157,7 +157,6 @@ Graphics::~Graphics()
 {
 	defaultConstantTexCoord.set(nullptr);
 	defaultConstantColor.set(nullptr);
-	defaultTexture.set(nullptr);
 
 	Volatile::unloadAll();
 	cleanup();
@@ -676,7 +675,6 @@ bool Graphics::setMode(void *context, int width, int height, int pixelwidth, int
 			defaultConstantColor = newBuffer(settings, { format }, whiteColor, sizeof(whiteColor), 1);
 		}
 
-		createDefaultTexture();
 		createDefaultShaders();
 		Shader::current = Shader::standardShaders[Shader::StandardShader::STANDARD_DEFAULT];
 		createQuadIndexBuffer();
@@ -2384,10 +2382,7 @@ void Graphics::prepareDraw(const VertexAttributes &attributes, const BufferBindi
 		offsets.push_back((VkDeviceSize)0);
 	}
 
-	if (texture == nullptr)
-		configuration.shader->setMainTex(defaultTexture);
-	else
-		configuration.shader->setMainTex(texture);
+	configuration.shader->setMainTex(texture);
 
 	ensureGraphicsPipelineConfiguration(configuration);
 
@@ -3035,15 +3030,6 @@ void Graphics::createSyncObjects()
 			throw love::Exception("failed to create synchronization objects for a frame!");
 }
 
-void Graphics::createDefaultTexture()
-{
-	Texture::Settings settings;
-	defaultTexture.set(newTexture(settings, nullptr), Acquire::NORETAIN);
-
-	uint8_t whitePixels[] = {255, 255, 255, 255};
-	defaultTexture->replacePixels(whitePixels, sizeof(whitePixels), 0, 0, { 0, 0, 1, 1 }, false);
-}
-
 void Graphics::cleanup()
 {
 	for (auto &cleanUpFns : cleanUpFunctions)

+ 0 - 2
src/modules/graphics/vulkan/Graphics.h

@@ -367,7 +367,6 @@ private:
 	void createCommandPool();
 	void createCommandBuffers();
 	void createSyncObjects();
-	void createDefaultTexture();
 	void cleanup();
 	void cleanupSwapChain();
 	void recreateSwapChain();
@@ -442,7 +441,6 @@ private:
 	bool swapChainRecreationRequested = false;
 	bool transitionColorDepthLayouts = false;
 	VmaAllocator vmaAllocator = VK_NULL_HANDLE;
-	StrongRef<love::graphics::Texture> defaultTexture;
 	StrongRef<love::graphics::Buffer> defaultConstantColor;
 	StrongRef<love::graphics::Buffer> defaultConstantTexCoord;
 	// functions that need to be called to cleanup objects that were needed for rendering a frame.