Skip to content

Instantly share code, notes, and snippets.

@iUltimateLP
Last active February 13, 2024 12:58
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save iUltimateLP/baca7aee4b28585b5fd2d0d46b541d95 to your computer and use it in GitHub Desktop.
Save iUltimateLP/baca7aee4b28585b5fd2d0d46b541d95 to your computer and use it in GitHub Desktop.
Implements dynamic textures into Unreal Engine 4, which can be dynamically written at runtime using the fastest way possible: by directly manipulating the pixel buffer of the texture.
// DynamicTexture
#include "DynamicTexture.h"
// UTextures have a BPP of 4 (Red, Green, Blue, Alpha)
#define DYNAMIC_TEXTURE_BYTES_PER_PIXEL 4
void UDynamicTexture::Initialize(int32 InWidth, int32 InHeight, FLinearColor InClearColor, TextureFilter FilterMethod/* = TextureFilter::TF_Nearest*/)
{
// Store the parameters
TextureWidth = InWidth;
TextureHeight = InHeight;
ClearColor = InClearColor;
// Create the UTexture2D to render to
Texture = UTexture2D::CreateTransient(TextureWidth, TextureHeight);
Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap; // The VectorDisplacementMap is a raw RGBA8 format
Texture->SRGB = 1;
Texture->Filter = FilterMethod;
Texture->UpdateResource();
// Create the proxy object for updating the texture region
UpdateTextureRegionProxy = MakeUnique<FUpdateTextureRegion2D>(0, 0, 0, 0, TextureWidth, TextureHeight);
// Size of the image pixel buffer
SIZE_T BufferSize = TextureWidth * TextureHeight * DYNAMIC_TEXTURE_BYTES_PER_PIXEL;
PixelBuffer = MakeUnique<uint8[]>(BufferSize);
// Initially clear the texture
Clear();
}
void UDynamicTexture::SetPixel(int32 X, int32 Y, FLinearColor Color)
{
// Get the pointer of the specified pixel
uint8* Ptr = GetPointerToPixel(X, Y);
// Set the pixel (note that linear color uses floats between 0..1, but a uint8 ranges from 0..255)
SetPixelInternal(Ptr, Color.R * 255, Color.G * 255, Color.B * 255, Color.A * 255);
}
void UDynamicTexture::Fill(FLinearColor Color)
{
// Get the base pointer of the pixel buffer
uint8* Ptr = PixelBuffer.Get();
// Iterate over all pixels
for (int i = 0; i < TextureWidth * TextureHeight; ++i)
{
// Set the pixel
SetPixelInternal(Ptr, Color.R * 255, Color.G * 255, Color.B * 255, Color.A * 255);
// Advance to the next pixel
Ptr += DYNAMIC_TEXTURE_BYTES_PER_PIXEL;
}
}
void UDynamicTexture::FillRect(int32 X, int32 Y, int32 Width, int32 Height, FLinearColor Color)
{
// Will hold the current pixel
uint8* Ptr = NULL;
// Loop over the Y and X region
for (int y = Y; y < Y + Height; y++)
{
for (int x = X; x < X + Width; x++)
{
// Get the current pixel pointer
Ptr = GetPointerToPixel(x, y);
// Set the pixel
SetPixelInternal(Ptr, Color.R * 255, Color.G * 255, Color.B * 255, Color.A * 255);
}
}
}
void UDynamicTexture::DrawLine(int32 X1, int32 Y1, int32 X2, int32 Y2, FLinearColor Color)
{
// Bresenham's line algorithm taken from here: http://members.chello.at/~easyfilter/bresenham.html
int X = X1;
int Y = Y1;
int dx = abs(X2 - X1), sx = X1 < X2 ? 1 : -1;
int dy = -abs(Y2 - Y1), sy = Y1 < Y2 ? 1 : -1;
int err = dx + dy, e2; // error value e_xy
for (;;)
{
SetPixel(X, Y, Color);
if (X == X2 && Y == Y2) break;
e2 = 2 * err;
if (e2 >= dy) { err += dy; X += sx; } // e_xy+e_x > 0
if (e2 <= dx) { err += dx; Y += sy; } // e_xy+e_y < 0
}
}
void UDynamicTexture::Clear()
{
// Fill with the clear color
Fill(ClearColor);
}
UTexture2D* UDynamicTexture::GetTextureResource()
{
return Texture;
}
void UDynamicTexture::SetPixelInternal(uint8*& Ptr, uint8 Red, uint8 Green, uint8 Blue, uint8 Alpha)
{
// Pixels are stored in BGRA format
*Ptr = Blue;
*(Ptr + 1) = Green;
*(Ptr + 2) = Red;
*(Ptr + 3) = Alpha;
}
uint8* UDynamicTexture::GetPointerToPixel(int32 X, int32 Y)
{
// The calculation of the pointer address of a given pixel is
// base + ((x + (y * width)) * bpp)
return (PixelBuffer.Get() + ((X + (Y * TextureWidth)) * DYNAMIC_TEXTURE_BYTES_PER_PIXEL));
}
void UDynamicTexture::UpdateTexture()
{
// Make sure the proxy and the texture is valid
if (UpdateTextureRegionProxy.IsValid() && Texture)
{
// Update the texture's regions
Texture->UpdateTextureRegions(
0, // Mip index
1, // Number of regions
UpdateTextureRegionProxy.Get(), // Region proxy
TextureWidth * DYNAMIC_TEXTURE_BYTES_PER_PIXEL, // Source data pitch
DYNAMIC_TEXTURE_BYTES_PER_PIXEL, // Bytes per pixel of source data
PixelBuffer.Get() // Buffer of pixels to set
);
}
}
int32 UDynamicTexture::GetWidth()
{
return TextureWidth;
}
int32 UDynamicTexture::GetHeight()
{
return TextureHeight;
}
// DynamicTexture
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "RHI.h"
#include "DynamicTexture.generated.h"
/*
This implements a fast dynamic texture without the use of the
UE4 Slate canvas features, which are way to slow for real-time
use. Therefore, this writes into the texture's memory directly.
*/
UCLASS()
class UDynamicTexture : public UObject
{
GENERATED_BODY()
public:
// Initializes the dynamic texture with given dimensions
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void Initialize(int32 InWidth, int32 InHeight, FLinearColor InClearColor, TextureFilter FilterMethod = TextureFilter::TF_Nearest);
// Sets a specified pixel to a color
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void SetPixel(int32 X, int32 Y, FLinearColor Color);
// Fills the texture with a given color
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void Fill(FLinearColor Color);
// Fills a rectangle area of the texture with a given color
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void FillRect(int32 X, int32 Y, int32 Width, int32 Height, FLinearColor Color);
// Draws a line between two points
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void DrawLine(int32 X1, int32 Y1, int32 X2, int32 Y2, FLinearColor Color);
// Clears the canvas (same as filling with the clear color)
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void Clear();
// Returns the UTexture resource which is used as a canvas
UFUNCTION(BlueprintPure, Category = "Dynamic Texture")
UTexture2D* GetTextureResource();
// Needs to be called at the end of each drawing operation to update the texture
// You can also call this at the end of multiple drawing operations, so the UTexture
// does not get updated more than needed.
UFUNCTION(BlueprintCallable, Category = "Dynamic Texture")
void UpdateTexture();
// Returns the width of this texture
UFUNCTION(BlueprintPure, Category = "Dynamic Texture")
int32 GetWidth();
// Returns the height of this texture
UFUNCTION(BlueprintPure, Category = "Dynamic Texture")
int32 GetHeight();
private:
// Internal function to set a pixel in the image
void SetPixelInternal(uint8*& Ptr, uint8 Red, uint8 Green, uint8 Blue, uint8 Alpha);
// Internal function to return the pointer pointing to the specified pixel
uint8* GetPointerToPixel(int32 X, int32 Y);
private:
// Reference to the UTexture2D* were drawing to
UPROPERTY()
UTexture2D* Texture;
// The Dimensions of the image we're drawing
int32 TextureWidth;
int32 TextureHeight;
// The clear color of the canvas
FLinearColor ClearColor;
// Unique pointer to the raw pixel data of the texture
TUniquePtr<uint8[]> PixelBuffer;
// Unique pointer to the proxy used to update the texture region (not really using regions,
// it's one "big" region)
TUniquePtr<FUpdateTextureRegion2D> UpdateTextureRegionProxy;
};
@iUltimateLP
Copy link
Author

@LinusBF
Copy link

LinusBF commented Sep 24, 2022

Hi!

Thanks a lot for this gist!

FYI, I just copy pasted it into a UE5 project and it was complaining about missing #include "Engine/Texture.h" as a dependency for the TextureFilter reference :)

@ddurwood
Copy link

Thanks!

You just saved me a few hours (plus debug time!) implementing this exact class.

I appreciate it!

@phil123456
Copy link

fastest way possible ? calling a function for each pixel draw call
meh

@PerhapsThat
Copy link

if i want to use PF_FloatRGBA Pixelformat, how can I modify ur code?

@hanbim520
Copy link

just GPixelFormats[PF_FloatRGBA ].BlockBytes @PerhapsThat

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment