Skip to content

Commit 6fbc481

Browse files
Use output texture for readback to avoid copy
1 parent 7d50cf9 commit 6fbc481

2 files changed

Lines changed: 36 additions & 64 deletions

File tree

src/ImageSharp.Drawing.WebGPU/WebGPUDrawingBackend.cs

Lines changed: 29 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -437,75 +437,26 @@ private bool TryRenderPreparedFlush<TPixel>(
437437
return false;
438438
}
439439

440+
// Use the target texture directly as the backdrop source.
441+
// This avoids an extra texture allocation and target→source copy.
440442
TextureView* backdropTextureView = flushContext.TargetView;
441443
int sourceOriginX = targetLocalBounds.X;
442444
int sourceOriginY = targetLocalBounds.Y;
443-
Texture* outputTexture = flushContext.TargetTexture;
444-
TextureView* outputTextureView = flushContext.TargetView;
445-
bool writesDirectlyToTarget = !flushContext.RequiresReadback;
446-
bool copyOutputToTarget = !writesDirectlyToTarget;
447-
int outputOriginX = writesDirectlyToTarget ? targetLocalBounds.X : 0;
448-
int outputOriginY = writesDirectlyToTarget ? targetLocalBounds.Y : 0;
449-
if (writesDirectlyToTarget)
450-
{
451-
backdropTextureView = flushContext.TargetView;
452-
sourceOriginX = targetLocalBounds.X;
453-
sourceOriginY = targetLocalBounds.Y;
454-
if (!TryCreateCompositionTexture(
455-
flushContext,
456-
targetLocalBounds.Width,
457-
targetLocalBounds.Height,
458-
out outputTexture,
459-
out outputTextureView,
460-
out error))
461-
{
462-
return false;
463-
}
464-
465-
outputOriginX = 0;
466-
outputOriginY = 0;
467-
copyOutputToTarget = true;
468-
}
469-
else
470-
{
471-
if (!TryCreateCompositionTexture(
472-
flushContext,
473-
targetLocalBounds.Width,
474-
targetLocalBounds.Height,
475-
out Texture* sourceTexture,
476-
out backdropTextureView,
477-
out error))
478-
{
479-
return false;
480-
}
481445

482-
CopyTextureRegion(
446+
if (!TryCreateCompositionTexture(
483447
flushContext,
484-
flushContext.TargetTexture,
485-
targetLocalBounds.X,
486-
targetLocalBounds.Y,
487-
sourceTexture,
488-
0,
489-
0,
490448
targetLocalBounds.Width,
491-
targetLocalBounds.Height);
492-
sourceOriginX = 0;
493-
sourceOriginY = 0;
494-
if (!TryCreateCompositionTexture(
495-
flushContext,
496-
targetLocalBounds.Width,
497-
targetLocalBounds.Height,
498-
out outputTexture,
499-
out outputTextureView,
500-
out error))
501-
{
502-
return false;
503-
}
504-
505-
outputOriginX = 0;
506-
outputOriginY = 0;
449+
targetLocalBounds.Height,
450+
out Texture* outputTexture,
451+
out TextureView* outputTextureView,
452+
out error))
453+
{
454+
return false;
507455
}
508456

457+
int outputOriginX = 0;
458+
int outputOriginY = 0;
459+
509460
List<CompositionCoverageDefinition> coverageDefinitions = [];
510461
Dictionary<CoverageDefinitionIdentity, int> coverageDefinitionIndexByKey = [];
511462
int[] batchCoverageIndices = new int[preparedBatches.Count];
@@ -572,8 +523,15 @@ private bool TryRenderPreparedFlush<TPixel>(
572523
return false;
573524
}
574525

575-
if (copyOutputToTarget)
526+
if (flushContext.RequiresReadback)
576527
{
528+
// CPU target: read back directly from the output texture at (0,0)
529+
// instead of copying output→target and then reading from target.
530+
flushContext.ReadbackSourceOverride = outputTexture;
531+
}
532+
else
533+
{
534+
// Native GPU surface: copy composited output back into the target.
577535
CopyTextureRegion(
578536
flushContext,
579537
outputTexture,
@@ -1960,11 +1918,18 @@ flushContext.ReadbackBuffer is null ||
19601918
uint copyBytesPerRow = checked((uint)copyBounds.Width * (uint)Unsafe.SizeOf<TPixel>());
19611919
copyBytesPerRow = (copyBytesPerRow + 255U) & ~255U;
19621920

1921+
// When ReadbackSourceOverride is set, the output texture already contains the
1922+
// composited result at (0,0), so we read from there instead of the target texture.
1923+
bool useOverride = flushContext.ReadbackSourceOverride is not null;
1924+
Texture* readbackTexture = useOverride ? flushContext.ReadbackSourceOverride : flushContext.TargetTexture;
1925+
uint readbackOriginX = useOverride ? 0 : (uint)copyBounds.X;
1926+
uint readbackOriginY = useOverride ? 0 : (uint)copyBounds.Y;
1927+
19631928
ImageCopyTexture source = new()
19641929
{
1965-
Texture = flushContext.TargetTexture,
1930+
Texture = readbackTexture,
19661931
MipLevel = 0,
1967-
Origin = new Origin3D((uint)copyBounds.X, (uint)copyBounds.Y, 0),
1932+
Origin = new Origin3D(readbackOriginX, readbackOriginY, 0),
19681933
Aspect = TextureAspect.All
19691934
};
19701935

src/ImageSharp.Drawing.WebGPU/WebGPUFlushContext.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,13 @@ private WebGPUFlushContext(
122122
/// </summary>
123123
public bool RequiresReadback { get; private set; }
124124

125+
/// <summary>
126+
/// Gets or sets an optional override texture to read back from instead of <see cref="TargetTexture"/>.
127+
/// When set, readback copies from this texture at origin (0,0) rather than from the target
128+
/// at composition bounds, eliminating an intermediate texture-to-texture copy.
129+
/// </summary>
130+
public Texture* ReadbackSourceOverride { get; set; }
131+
125132
/// <summary>
126133
/// Gets a value indicating whether the current target texture can be sampled in a compute shader.
127134
/// </summary>

0 commit comments

Comments
 (0)