Browse Source

Backends: Handle premultiplied alpha for textures and vertices

Michael Ragazzon 2 years ago
parent
commit
2f80bdd163

+ 21 - 10
Backends/RmlUi_Backend_SDL_GL2.cpp

@@ -76,23 +76,25 @@ public:
 			return false;
 
 		file_interface->Seek(file_handle, 0, SEEK_END);
-		size_t buffer_size = file_interface->Tell(file_handle);
+		const size_t buffer_size = file_interface->Tell(file_handle);
 		file_interface->Seek(file_handle, 0, SEEK_SET);
 
-		Rml::UniquePtr<char[]> buffer(new char[buffer_size]);
+		using Rml::byte;
+		Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
 		file_interface->Read(buffer.get(), buffer_size, file_handle);
 		file_interface->Close(file_handle);
 
-		const size_t i = source.rfind('.');
-		Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1));
+		const size_t i_ext = source.rfind('.');
+		Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
 		SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
 		if (!surface)
 			return false;
 
-		if (surface->format->Amask == 0)
+		if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32)
 		{
-			// Fix for rendering images with no alpha channel, see https://github.com/mikke89/RmlUi/issues/239
+			// Ensure correct format for premultiplied alpha conversion below. Additionally, fix rendering images with
+			// no alpha channel, see https://github.com/mikke89/RmlUi/issues/239
 			SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32, 0);
 			SDL_FreeSurface(surface);
 
@@ -102,15 +104,24 @@ public:
 			surface = converted_surface;
 		}
 
-		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
-		if (texture)
+		// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+		byte* pixels = static_cast<byte*>(surface->pixels);
+		for (int i = 0; i < surface->w * surface->h * 4; i += 4)
 		{
-			texture_handle = (Rml::TextureHandle)texture;
-			texture_dimensions = Rml::Vector2i(surface->w, surface->h);
+			const byte alpha = pixels[i + 3];
+			for (int j = 0; j < 3; ++j)
+				pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
 		}
 
+		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
+
+		texture_dimensions = Rml::Vector2i(surface->w, surface->h);
+		texture_handle = (Rml::TextureHandle)texture;
 		SDL_FreeSurface(surface);
 
+		if (!texture)
+			return false;
+
 		return true;
 	}
 

+ 23 - 29
Backends/RmlUi_Backend_SDL_GL3.cpp

@@ -72,42 +72,36 @@ public:
 		Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
 		SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
+		if (!surface)
+			return false;
 
-		bool success = false;
-		if (surface)
-		{
-			texture_dimensions.x = surface->w;
-			texture_dimensions.y = surface->h;
-
-			if (surface->format->format != SDL_PIXELFORMAT_RGBA32)
-			{
-				SDL_SetSurfaceAlphaMod(surface, SDL_ALPHA_OPAQUE);
-				SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
-
-				SDL_Surface* new_surface = SDL_CreateRGBSurfaceWithFormat(0, surface->w, surface->h, 32, SDL_PIXELFORMAT_RGBA32);
-				if (!new_surface)
-					return false;
+		texture_dimensions.x = surface->w;
+		texture_dimensions.y = surface->h;
 
-				if (SDL_BlitSurface(surface, 0, new_surface, 0) != 0)
-					return false;
+		if (surface->format->format != SDL_PIXELFORMAT_RGBA32)
+		{
+			// Ensure correct format for premultiplied alpha conversion and GenerateTexture below.
+			SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PixelFormatEnum::SDL_PIXELFORMAT_RGBA32, 0);
+			SDL_FreeSurface(surface);
 
-				SDL_FreeSurface(surface);
-				surface = new_surface;
-			}
+			if (!converted_surface)
+				return false;
 
-			// RmlUi assumes premultiplied alpha, convert the color values accordingly.
-			byte* pixels = static_cast<byte*>(surface->pixels);
-			for (int i = 0; i < surface->w * surface->h * 4; i += 4)
-			{
-				const byte alpha = pixels[i + 3];
-				for (int j = 0; j < 3; ++j)
-					pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
-			}
+			surface = converted_surface;
+		}
 
-			success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions);
-			SDL_FreeSurface(surface);
+		// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+		byte* pixels = static_cast<byte*>(surface->pixels);
+		for (int i = 0; i < surface->w * surface->h * 4; i += 4)
+		{
+			const byte alpha = pixels[i + 3];
+			for (int j = 0; j < 3; ++j)
+				pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
 		}
 
+		bool success = RenderInterface_GL3::GenerateTexture(texture_handle, (const Rml::byte*)surface->pixels, texture_dimensions);
+		SDL_FreeSurface(surface);
+
 		return success;
 	}
 };

+ 27 - 13
Backends/RmlUi_Backend_SFML_GL2.cpp

@@ -69,28 +69,41 @@ public:
 		size_t buffer_size = file_interface->Tell(file_handle);
 		file_interface->Seek(file_handle, 0, SEEK_SET);
 
-		char* buffer = new char[buffer_size];
-		file_interface->Read(buffer, buffer_size, file_handle);
+		using Rml::byte;
+		Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+		file_interface->Read(buffer.get(), buffer_size, file_handle);
 		file_interface->Close(file_handle);
 
-		sf::Texture* texture = new sf::Texture();
-		texture->setSmooth(true);
-
-		bool success = texture->loadFromMemory(buffer, buffer_size);
-
-		delete[] buffer;
+		sf::Image image;
+		if (!image.loadFromMemory(buffer.get(), buffer_size))
+			return false;
 
-		if (success)
+		// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+		for (unsigned int x = 0; x < image.getSize().x; x++)
 		{
-			texture_handle = (Rml::TextureHandle)texture;
-			texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y);
+			for (unsigned int y = 0; y < image.getSize().y; y++)
+			{
+				sf::Color color = image.getPixel(x, y);
+				color.r = (sf::Uint8)((color.r * color.a) / 255);
+				color.g = (sf::Uint8)((color.g * color.a) / 255);
+				color.b = (sf::Uint8)((color.b * color.a) / 255);
+				image.setPixel(x, y, color);
+			}
 		}
-		else
+
+		sf::Texture* texture = new sf::Texture();
+		texture->setSmooth(true);
+
+		if (!texture->loadFromImage(image))
 		{
 			delete texture;
+			return false;
 		}
 
-		return success;
+		texture_handle = (Rml::TextureHandle)texture;
+		texture_dimensions = Rml::Vector2i(texture->getSize().x, texture->getSize().y);
+
+		return true;
 	}
 
 	bool GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions) override
@@ -105,6 +118,7 @@ public:
 		}
 
 		texture->update(source, source_dimensions.x, source_dimensions.y, 0, 0);
+
 		texture_handle = (Rml::TextureHandle)texture;
 
 		return true;

+ 16 - 14
Backends/RmlUi_Renderer_GL2.cpp

@@ -70,7 +70,7 @@ void RenderInterface_GL2::BeginFrame()
 	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 
 	glEnable(GL_BLEND);
-	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+	glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
 
 	Rml::Matrix4f projection = Rml::Matrix4f::ProjectOrtho(0, (float)viewport_width, (float)viewport_height, 0, -10000, 10000);
 	glMatrixMode(GL_PROJECTION);
@@ -222,12 +222,13 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 		return false;
 	}
 
-	char* buffer = new char[buffer_size];
-	file_interface->Read(buffer, buffer_size, file_handle);
+	using Rml::byte;
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
 	TGAHeader header;
-	memcpy(&header, buffer, sizeof(TGAHeader));
+	memcpy(&header, buffer.get(), sizeof(TGAHeader));
 
 	int color_mode = header.bitsPerPixel / 8;
 	int image_size = header.width * header.height * 4; // We always make 32bit textures
@@ -235,7 +236,6 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	if (header.dataType != 2)
 	{
 		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
-		delete[] buffer;
 		return false;
 	}
 
@@ -243,25 +243,30 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	if (color_mode < 3)
 	{
 		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
-		delete[] buffer;
 		return false;
 	}
 
-	const char* image_src = buffer + sizeof(TGAHeader);
-	unsigned char* image_dest = new unsigned char[image_size];
+	const byte* image_src = buffer.get() + sizeof(TGAHeader);
+	Rml::UniquePtr<byte[]> image_dest_buffer(new byte[image_size]);
+	byte* image_dest = image_dest_buffer.get();
 
-	// Targa is BGR, swap to RGB and flip Y axis
+	// Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha.
 	for (long y = 0; y < header.height; y++)
 	{
 		long read_index = y * header.width * color_mode;
-		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * color_mode;
+		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4;
 		for (long x = 0; x < header.width; x++)
 		{
 			image_dest[write_index] = image_src[read_index + 2];
 			image_dest[write_index + 1] = image_src[read_index + 1];
 			image_dest[write_index + 2] = image_src[read_index];
 			if (color_mode == 4)
-				image_dest[write_index + 3] = image_src[read_index + 3];
+			{
+				const byte alpha = image_src[read_index + 3];
+				for (size_t j = 0; j < 3; j++)
+					image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255);
+				image_dest[write_index + 3] = alpha;
+			}
 			else
 				image_dest[write_index + 3] = 255;
 
@@ -275,9 +280,6 @@ bool RenderInterface_GL2::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 
 	bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
 
-	delete[] image_dest;
-	delete[] buffer;
-
 	return success;
 }
 

+ 18 - 15
Backends/RmlUi_Renderer_GL3.cpp

@@ -228,8 +228,15 @@ in vec2 fragTexCoord;
 out vec4 finalColor;
 
 void main() {
+	// The general case uses a 4x5 color matrix for full rgba transformation, plus a constant term with the last column.
+	// However, we only consider the case of rgb transformations. Thus, we could in principle use a 3x4 matrix, but we
+	// keep the alpha row for simplicity.
+	// In the general case we should do the matrix transformation in non-premultiplied space. However, without alpha
+	// transformations, we can do it directly in premultiplied space to avoid the extra division and multiplication
+	// steps. In this space, the constant term needs to be multiplied by the alpha value, instead of unity.
 	vec4 texColor = texture(_tex, fragTexCoord);
-	finalColor = _color_matrix * texColor;
+	vec3 transformedColor = vec3(_color_matrix * texColor);
+	finalColor = vec4(transformedColor, texColor.a);
 }
 )";
 static const char* shader_frag_blend_mask = RMLUI_SHADER_HEADER R"(
@@ -1198,12 +1205,12 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	}
 
 	using Rml::byte;
-	byte* buffer = new byte[buffer_size];
-	file_interface->Read(buffer, buffer_size, file_handle);
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
 	TGAHeader header;
-	memcpy(&header, buffer, sizeof(TGAHeader));
+	memcpy(&header, buffer.get(), sizeof(TGAHeader));
 
 	int color_mode = header.bitsPerPixel / 8;
 	int image_size = header.width * header.height * 4; // We always make 32bit textures
@@ -1211,7 +1218,6 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	if (header.dataType != 2)
 	{
 		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24/32bit uncompressed TGAs are supported.");
-		delete[] buffer;
 		return false;
 	}
 
@@ -1219,28 +1225,28 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	if (color_mode < 3)
 	{
 		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
-		delete[] buffer;
 		return false;
 	}
 
-	const byte* image_src = buffer + sizeof(TGAHeader);
-	byte* image_dest = new byte[image_size];
+	const byte* image_src = buffer.get() + sizeof(TGAHeader);
+	Rml::UniquePtr<byte[]> image_dest_buffer(new byte[image_size]);
+	byte* image_dest = image_dest_buffer.get();
 
-	// Targa is BGR, swap to RGB and flip Y axis
+	// Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha.
 	for (long y = 0; y < header.height; y++)
 	{
 		long read_index = y * header.width * color_mode;
 		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4;
 		for (long x = 0; x < header.width; x++)
 		{
-			image_dest[write_index] = image_src[read_index + 1];
-			image_dest[write_index + 1] = image_src[read_index + 2];
+			image_dest[write_index] = image_src[read_index + 2];
+			image_dest[write_index + 1] = image_src[read_index + 1];
 			image_dest[write_index + 2] = image_src[read_index];
 			if (color_mode == 4)
 			{
 				const byte alpha = image_src[read_index + 3];
 				for (size_t j = 0; j < 3; j++)
-					image_dest[write_index + j] = byte((int(image_dest[write_index + j]) * int(alpha)) / 255);
+					image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255);
 				image_dest[write_index + 3] = alpha;
 			}
 			else
@@ -1256,9 +1262,6 @@ bool RenderInterface_GL3::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 
 	bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
 
-	delete[] image_dest;
-	delete[] buffer;
-
 	return success;
 }
 

+ 47 - 35
Backends/RmlUi_Renderer_SDL.cpp

@@ -33,14 +33,20 @@
 #include <SDL.h>
 #include <SDL_image.h>
 
-RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer) {}
+RenderInterface_SDL::RenderInterface_SDL(SDL_Renderer* renderer) : renderer(renderer)
+{
+	// RmlUi serves vertex colors and textures with premultiplied alpha, set the blend mode accordingly.
+	// Equivalent to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA).
+	blend_mode = SDL_ComposeCustomBlendMode(SDL_BLENDFACTOR_ONE, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD, SDL_BLENDFACTOR_ONE,
+		SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD);
+}
 
 void RenderInterface_SDL::BeginFrame()
 {
 	SDL_RenderSetViewport(renderer, nullptr);
 	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
 	SDL_RenderClear(renderer);
-	SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
+	SDL_SetRenderDrawBlendMode(renderer, blend_mode);
 }
 
 void RenderInterface_SDL::EndFrame() {}
@@ -96,54 +102,60 @@ bool RenderInterface_SDL::LoadTexture(Rml::TextureHandle& texture_handle, Rml::V
 	size_t buffer_size = file_interface->Tell(file_handle);
 	file_interface->Seek(file_handle, 0, SEEK_SET);
 
-	char* buffer = new char[buffer_size];
-	file_interface->Read(buffer, buffer_size, file_handle);
+	using Rml::byte;
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
-	const size_t i = source.rfind('.');
-	Rml::String extension = (i == Rml::String::npos ? Rml::String() : source.substr(i + 1));
-
-	SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer, int(buffer_size)), 1, extension.c_str());
+	const size_t i_ext = source.rfind('.');
+	Rml::String extension = (i_ext == Rml::String::npos ? Rml::String() : source.substr(i_ext + 1));
 
-	bool success = false;
+	SDL_Surface* surface = IMG_LoadTyped_RW(SDL_RWFromMem(buffer.get(), int(buffer_size)), 1, extension.c_str());
+	if (!surface)
+		return false;
 
-	if (surface)
+	if (surface->format->format != SDL_PIXELFORMAT_RGBA32 && surface->format->format != SDL_PIXELFORMAT_BGRA32)
 	{
-		SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
+		SDL_Surface* converted_surface = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32, 0);
+		SDL_FreeSurface(surface);
 
-		if (texture)
-		{
-			texture_handle = (Rml::TextureHandle)texture;
-			texture_dimensions = Rml::Vector2i(surface->w, surface->h);
-			success = true;
-		}
+		if (!converted_surface)
+			return false;
 
-		SDL_FreeSurface(surface);
+		surface = converted_surface;
 	}
 
-	delete[] buffer;
+	// Convert colors to premultiplied alpha, which is necessary for correct alpha compositing.
+	byte* pixels = static_cast<byte*>(surface->pixels);
+	for (int i = 0; i < surface->w * surface->h * 4; i += 4)
+	{
+		const byte alpha = pixels[i + 3];
+		for (int j = 0; j < 3; ++j)
+			pixels[i + j] = byte(int(pixels[i + j]) * int(alpha) / 255);
+	}
+
+	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
 
-	return success;
+	texture_dimensions = Rml::Vector2i(surface->w, surface->h);
+	texture_handle = (Rml::TextureHandle)texture;
+	SDL_FreeSurface(surface);
+
+	if (!texture)
+		return false;
+
+	SDL_SetTextureBlendMode(texture, blend_mode);
+
+	return true;
 }
 
 bool RenderInterface_SDL::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions)
 {
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
-	Uint32 rmask = 0xff000000;
-	Uint32 gmask = 0x00ff0000;
-	Uint32 bmask = 0x0000ff00;
-	Uint32 amask = 0x000000ff;
-#else
-	Uint32 rmask = 0x000000ff;
-	Uint32 gmask = 0x0000ff00;
-	Uint32 bmask = 0x00ff0000;
-	Uint32 amask = 0xff000000;
-#endif
-
-	SDL_Surface* surface =
-		SDL_CreateRGBSurfaceFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4, rmask, gmask, bmask, amask);
+	SDL_Surface* surface = SDL_CreateRGBSurfaceWithFormatFrom((void*)source, source_dimensions.x, source_dimensions.y, 32, source_dimensions.x * 4,
+		SDL_PIXELFORMAT_RGBA32);
+
 	SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
-	SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
+	SDL_SetTextureBlendMode(texture, blend_mode);
+
 	SDL_FreeSurface(surface);
 	texture_handle = (Rml::TextureHandle)texture;
 	return true;

+ 1 - 0
Backends/RmlUi_Renderer_SDL.h

@@ -54,6 +54,7 @@ public:
 
 private:
 	SDL_Renderer* renderer;
+	SDL_BlendMode blend_mode = {};
 	SDL_Rect rect_scissor = {};
 	bool scissor_region_enabled = false;
 };

+ 21 - 17
Backends/RmlUi_Renderer_VK.cpp

@@ -427,19 +427,20 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve
 	size_t buffer_size = file_interface->Tell(file_handle);
 	file_interface->Seek(file_handle, 0, SEEK_SET);
 
-	RMLUI_ASSERTMSG(buffer_size > sizeof(TGAHeader), "Texture file size is smaller than TGAHeader, file must be corrupt or otherwise invalid");
 	if (buffer_size <= sizeof(TGAHeader))
 	{
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Texture file size is smaller than TGAHeader, file is not a valid TGA image.");
 		file_interface->Close(file_handle);
 		return false;
 	}
 
-	char* buffer = new char[buffer_size];
-	file_interface->Read(buffer, buffer_size, file_handle);
+	using Rml::byte;
+	Rml::UniquePtr<byte[]> buffer(new byte[buffer_size]);
+	file_interface->Read(buffer.get(), buffer_size, file_handle);
 	file_interface->Close(file_handle);
 
 	TGAHeader header;
-	memcpy(&header, buffer, sizeof(TGAHeader));
+	memcpy(&header, buffer.get(), sizeof(TGAHeader));
 
 	int color_mode = header.bitsPerPixel / 8;
 	int image_size = header.width * header.height * 4; // We always make 32bit textures
@@ -453,25 +454,31 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve
 	// Ensure we have at least 3 colors
 	if (color_mode < 3)
 	{
-		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported");
+		Rml::Log::Message(Rml::Log::LT_ERROR, "Only 24 and 32bit textures are supported.");
 		return false;
 	}
 
-	const char* image_src = buffer + sizeof(TGAHeader);
-	unsigned char* image_dest = new unsigned char[image_size];
+	const byte* image_src = buffer.get() + sizeof(TGAHeader);
+	Rml::UniquePtr<byte[]> image_dest_buffer(new byte[image_size]);
+	byte* image_dest = image_dest_buffer.get();
 
-	// Targa is BGR, swap to RGB and flip Y axis
+	// Targa is BGR, swap to RGB, flip Y axis, and convert to premultiplied alpha.
 	for (long y = 0; y < header.height; y++)
 	{
 		long read_index = y * header.width * color_mode;
-		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * color_mode;
+		long write_index = ((header.imageDescriptor & 32) != 0) ? read_index : (header.height - y - 1) * header.width * 4;
 		for (long x = 0; x < header.width; x++)
 		{
 			image_dest[write_index] = image_src[read_index + 2];
 			image_dest[write_index + 1] = image_src[read_index + 1];
 			image_dest[write_index + 2] = image_src[read_index];
 			if (color_mode == 4)
-				image_dest[write_index + 3] = image_src[read_index + 3];
+			{
+				const byte alpha = image_src[read_index + 3];
+				for (size_t j = 0; j < 3; j++)
+					image_dest[write_index + j] = byte((image_dest[write_index + j] * alpha) / 255);
+				image_dest[write_index + 3] = alpha;
+			}
 			else
 				image_dest[write_index + 3] = 255;
 
@@ -483,12 +490,9 @@ bool RenderInterface_VK::LoadTexture(Rml::TextureHandle& texture_handle, Rml::Ve
 	texture_dimensions.x = header.width;
 	texture_dimensions.y = header.height;
 
-	bool status = CreateTexture(texture_handle, image_dest, texture_dimensions, source);
-
-	delete[] image_dest;
-	delete[] buffer;
+	bool success = GenerateTexture(texture_handle, image_dest, texture_dimensions);
 
-	return status;
+	return success;
 }
 
 bool RenderInterface_VK::GenerateTexture(Rml::TextureHandle& texture_handle, const Rml::byte* source, const Rml::Vector2i& source_dimensions)
@@ -1961,10 +1965,10 @@ void RenderInterface_VK::Create_Pipelines() noexcept
 	VkPipelineColorBlendAttachmentState info_color_blend_att = {};
 	info_color_blend_att.colorWriteMask = 0xf;
 	info_color_blend_att.blendEnable = VK_TRUE;
-	info_color_blend_att.srcColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_SRC_ALPHA;
+	info_color_blend_att.srcColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE;
 	info_color_blend_att.dstColorBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
 	info_color_blend_att.colorBlendOp = VkBlendOp::VK_BLEND_OP_ADD;
-	info_color_blend_att.srcAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_SRC_ALPHA;
+	info_color_blend_att.srcAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE;
 	info_color_blend_att.dstAlphaBlendFactor = VkBlendFactor::VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
 	info_color_blend_att.alphaBlendOp = VkBlendOp::VK_BLEND_OP_SUBTRACT;