Table of Contents

Class TexturePixelMemoryManager

Namespace
Sdl3Sharp.Video.Rendering
Assembly
Sdl3Sharp.dll

Manages and represents the entire pixel memory of a locked Texture or a partial rectangle of it

public abstract class TexturePixelMemoryManager : NativeMemoryManagerBase, IDisposable
Inheritance
TexturePixelMemoryManager
Implements
Derived
Inherited Members

Properties

IsPinned

Gets a value indicating whether the Texture is currently locked (pinned)

public override abstract bool IsPinned { get; }

Property Value

bool

true if the Texture is currently locked (pinned); otherwise, false (Texture is null in that case)

Length

Gets the number of bytes in the allocated memory region this NativeMemoryManagerBase represents

public override sealed nuint Length { get; }

Property Value

nuint

The number of bytes in the allocated memory region this NativeMemoryManagerBase represents

Memory

Gets a NativeMemory representing the pixel memory of the locked Texture

public override sealed NativeMemory Memory { get; }

Property Value

NativeMemory

A NativeMemory representing the pixel memory of the locked Texture, or Empty if the Texture is not locked

Examples

This leads to the following row-wise access pattern:

Texture texture;
Rect<int> rect;

...

if (texture.TryLock(rect, out var pixelManager))
{
	using (pixelManager)
	{
		var memory = (NativeMemory<byte>)pixelManager.Memory;	
		var bytesPerPixel = (nuint)pixelManager.Texture!.Format.BytesPerPixel;		

		for (nuint row = 0; row < pixelManager.RowCount; row++)
		{
			var pixels = memory.Slice(0, pixelManager.RowLength * bytesPerPixel).Span;

			// Process the pixels in this row
			...

			memory = memory.Slice(pixelManager.Pitch);
		}
	}
}

You can also access individual pixels directly using the following pattern:

Texture texture;
Rect<int> rect;

...

if (texture.TryLock(rect, out var pixelManager))
{
	using (pixelManager)
	{
		var memory = (NativeMemory<byte>)pixelManager.Memory;
		var bytesPerPixel = (nuint)pixelManager.Texture!.Format.BytesPerPixel;

		nuint x, y;

		// Access the pixel at (x, y)
		ref var pixel = ref memory.Slice(y * pixelManager.Pitch + x * bytesPerPixel).Span[0];

		...
	}
}

Remarks

The pixel memory data is in the pixel format specified by the Format property of the Texture.

The pixel memory might be longer than the actual pixel data that's safe to access, due to padding at the end of each row. Especially if the Texture was locked with a rectangle smaller than the full size of the texture.

The pixel memory is laid out in rows, where each row is RowLength pixels long, and there are RowCount rows. Between the start of each row, there are Pitch bytes. The NativeMemory returned by this property starts at the beginning of the first row.

As an optimization, the pixel data made available for editing don't necessarily contain the old (at the time of locking) texture data.

Notice: The pixel memory is write-only, and if you need to keep a copy of the texture data you should do that at the application level.

You must dispose the TexturePixelMemoryManager to unlock the Texture and apply the changes made to the pixel data.

Pitch

Gets the pitch of the pixel memory, in bytes

public nuint Pitch { get; }

Property Value

nuint

The pitch of the pixel memory, in bytes

Remarks

The pitch is the length of a single row of pixel data in bytes, including any padding bytes at the end of the row. Notice that that's not necessarily equal to RowLength * Texture.Format.BytesPerPixel. Especially if the Texture was locked with a rectangle smaller than the full size of the texture.

Pointer

Gets a pointer to the start of the allocated memory region this NativeMemoryManagerBase represents

public override sealed nint Pointer { get; }

Property Value

nint

A pointer to the start of the allocated memory region this NativeMemoryManagerBase represents

RowCount

Gets the number of rows in the pixel memory, in pixels

public nuint RowCount { get; }

Property Value

nuint

The number of rows in the pixel memory, in pixels

RowLength

Gets the length of each row in the pixel memory, in pixels

public nuint RowLength { get; }

Property Value

nuint

The length of each row in the pixel memory, in pixels

Remarks

To get the length of each row in bytes, use RowLength * Texture.Format.BytesPerPixel.

Texture

Gets the locked texture

public abstract Texture? Texture { get; }

Property Value

Texture

The locked texture, or null if the texture is not locked

Methods

AddPin(ulong, ulong)

Adds a "pin" to this NativeMemoryManagerBase

protected override sealed void AddPin(ulong oldPinCounter, ulong newPinCounter)

Parameters

oldPinCounter ulong

The pin counter before it was increased

newPinCounter ulong

The current pin counter after it was increased

Remarks

This method is called each time the pin counter was successfully increased. You can override this method to implement custom logic that should be executed each time a "pin" is added to this NativeMemoryManagerBase.

If you want a more general pinning logic that only triggers when the first pin is added, you can check whether oldPinCounter is 0 and newPinCounter greater than 0 in your custom implementation.

When implementing this method, remember that it must work in conjunction with RemovePin(ulong, ulong).

There's no guarantee about when this method is called in relation to other threads pinning or unpinning this NativeMemoryManagerBase. Custom implementations must take care of thread-safetiness themselves if needed, especially in regards to the ordering of AddPin(ulong, ulong) and RemovePin(ulong, ulong) operations.

The only guarantee given is that this method is only ever called immediately after the pin counter was successfully increased by a single pinning operation.

DecreasePinCounter(ulong)

Decreases the pin counter

protected override sealed ulong DecreasePinCounter(ulong pinCounter)

Parameters

pinCounter ulong

The current pin counter

Returns

ulong

The new pin counter

Remarks

Override this method to implement custom pin counter decreasing step logic. You can even just ignore pinning altogether and always return the given pinCounter.

Notice that custom implementations of this method should ensure that the returned value is less than or equal to the given pinCounter. Also, when implementing this method, remember that the pin counter stepping must work in conjunction with IncreasePinCounter(ulong). Furthermore, there's no underflow preventing logic in the consumers of this method, so custom implementations should ensure that underflow doesn't happen.

Dispose(bool)

Disposes the TexturePixelMemoryManager, unlocking the associated Texture

protected override void Dispose(bool disposing)

Parameters

disposing bool

A value indicating whether this method is called from Dispose() (true) or from the finalizer (false)

Remarks

Calling this method unlocks the associated Texture, if it's still locked, applying any changes made to the pixel data, and making its pixel memory inaccessible until it's locked again.

See Also

IncreasePinCounter(ulong)

Increases the pin counter

protected override sealed ulong IncreasePinCounter(ulong pinCounter)

Parameters

pinCounter ulong

The current pin counter

Returns

ulong

The new pin counter

Remarks

Override this method to implement custom pin counter increasing step logic. You can even just ignore pinning altogether and always return the given pinCounter.

Notice that custom implementations of this method should ensure that the returned value is greater than or equal to the given pinCounter. Also, when implementing this method, remember that the pin counter stepping must work in conjunction with DecreasePinCounter(ulong). Furthermore, there's no overflow preventing logic in the consumers of this method, so custom implementations should ensure that overflow doesn't happen.

RemovePin(ulong, ulong)

Removes a "pin" to this NativeMemoryManagerBase

protected override sealed void RemovePin(ulong oldPinCounter, ulong newPinCounter)

Parameters

oldPinCounter ulong

The pin counter before it was decreased

newPinCounter ulong

The current pin counter after it was decreased

Remarks

This method is called each time the pin counter was successfully decreased. You can override this method to implement custom logic that should be executed each time a "pin" is removed from this NativeMemoryManagerBase.

If you want a more general pinning logic that only triggers when the last pin is removed, you can check whether oldPinCounter is greater than 0 and newPinCounter is 0 in your custom implementation.

When implementing this method, remember that it must work in conjunction with AddPin(ulong, ulong).

There's no guarantee about when this method is called in relation to other threads pinning or unpinning this NativeMemoryManagerBase. Custom implementations must take care of thread-safetiness themselves if needed, especially in regards to the ordering of AddPin(ulong, ulong) and RemovePin(ulong, ulong) operations.

The only guarantee given is that this method is only ever called immediately after the pin counter was successfully decreased by a single unpinning operation.