JBR-8448 Vulkan: Cleanup & fix Sw->Surface blit

This commit is contained in:
Nikita Gubarkov
2025-03-21 17:32:25 +01:00
committed by Alexey Ushakov
parent ac68b628c7
commit 8120022a66
4 changed files with 207 additions and 411 deletions

View File

@@ -48,7 +48,6 @@ import java.awt.image.BufferedImageOp;
import java.lang.annotation.Native;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static sun.java2d.pipe.BufferedOpCodes.BLIT;
@@ -59,90 +58,19 @@ import static java.awt.Transparency.TRANSLUCENT;
final class VKBlitLoops {
static void register() {
Blit blitIntArgbPreToSurface =
new VKSwToSurfaceBlit(SurfaceType.IntArgbPre,
VKSurfaceData.PF_INT_ARGB_PRE);
Blit blitIntArgbPreToTexture =
new VKSwToTextureBlit(SurfaceType.IntArgbPre,
VKSurfaceData.PF_INT_ARGB_PRE);
TransformBlit transformBlitIntArgbPreToSurface =
new VKSwToSurfaceTransform(SurfaceType.IntArgbPre,
VKSurfaceData.PF_INT_ARGB_PRE);
VKSurfaceToSwBlit blitSurfaceToIntArgbPre =
new VKSurfaceToSwBlit(VKFormat.B8G8R8A8_UNORM, TRANSLUCENT); // TODO this is a placeholder.
GraphicsPrimitive[] primitiveArray = {
// sw->surface ops
blitIntArgbPreToSurface,
new VKSwToSurfaceBlit(SurfaceType.IntRgb,
VKSurfaceData.PF_INT_RGB),
new VKSwToSurfaceBlit(SurfaceType.IntRgbx,
VKSurfaceData.PF_INT_RGBX),
new VKSwToSurfaceBlit(SurfaceType.IntBgr,
VKSurfaceData.PF_INT_BGR),
new VKSwToSurfaceBlit(SurfaceType.IntBgrx,
VKSurfaceData.PF_INT_BGRX),
new VKGeneralBlit(VKSurfaceData.VKSurface,
CompositeType.AnyAlpha,
blitIntArgbPreToSurface),
new VKAnyCompositeBlit(VKSurfaceData.VKSurface,
blitSurfaceToIntArgbPre,
blitSurfaceToIntArgbPre,
blitIntArgbPreToSurface),
new VKAnyCompositeBlit(SurfaceType.Any,
null,
blitSurfaceToIntArgbPre,
blitIntArgbPreToSurface),
new VKSwToSurfaceScale(SurfaceType.IntRgb,
VKSurfaceData.PF_INT_RGB),
new VKSwToSurfaceScale(SurfaceType.IntRgbx,
VKSurfaceData.PF_INT_RGBX),
new VKSwToSurfaceScale(SurfaceType.IntBgr,
VKSurfaceData.PF_INT_BGR),
new VKSwToSurfaceScale(SurfaceType.IntBgrx,
VKSurfaceData.PF_INT_BGRX),
new VKSwToSurfaceScale(SurfaceType.IntArgbPre,
VKSurfaceData.PF_INT_ARGB_PRE),
new VKSwToSurfaceTransform(SurfaceType.IntRgb,
VKSurfaceData.PF_INT_RGB),
new VKSwToSurfaceTransform(SurfaceType.IntRgbx,
VKSurfaceData.PF_INT_RGBX),
new VKSwToSurfaceTransform(SurfaceType.IntBgr,
VKSurfaceData.PF_INT_BGR),
new VKSwToSurfaceTransform(SurfaceType.IntBgrx,
VKSurfaceData.PF_INT_BGRX),
transformBlitIntArgbPreToSurface,
new VKGeneralTransformedBlit(transformBlitIntArgbPreToSurface),
// sw->texture ops
blitIntArgbPreToTexture,
new VKSwToTextureBlit(SurfaceType.IntRgb,
VKSurfaceData.PF_INT_RGB),
new VKSwToTextureBlit(SurfaceType.IntRgbx,
VKSurfaceData.PF_INT_RGBX),
new VKSwToTextureBlit(SurfaceType.IntBgr,
VKSurfaceData.PF_INT_BGR),
new VKSwToTextureBlit(SurfaceType.IntBgrx,
VKSurfaceData.PF_INT_BGRX),
new VKGeneralBlit(VKSurfaceData.VKTexture,
CompositeType.SrcNoEa,
blitIntArgbPreToTexture),
};
List<GraphicsPrimitive> primitives = new ArrayList<>();
Collections.addAll(primitives, primitiveArray);
// Surface->Surface
primitives.add(new VKSurfaceToSurfaceBlit(CompositeType.AnyAlpha));
primitives.add(new VKSurfaceToSurfaceBlit(CompositeType.Xor));
primitives.add(new VKSurfaceToSurfaceScale(CompositeType.AnyAlpha));
primitives.add(new VKSurfaceToSurfaceScale(CompositeType.Xor));
primitives.add(new VKSurfaceToSurfaceTransform(CompositeType.AnyAlpha));
primitives.add(new VKSurfaceToSurfaceTransform(CompositeType.Xor));
// Surface->Sw
VKSwToSurfaceBlitContext blitContext = new VKSwToSurfaceBlitContext();
for (CompositeType compositeType : new CompositeType[] { CompositeType.AnyAlpha, CompositeType.Xor }) {
// Sw->Surface
primitives.add(new VKSwToSurfaceBlit(compositeType, blitContext));
primitives.add(new VKSwToSurfaceScale(compositeType, blitContext));
primitives.add(new VKSwToSurfaceTransform(compositeType, blitContext));
// Surface->Surface
primitives.add(new VKSurfaceToSurfaceBlit(compositeType));
primitives.add(new VKSurfaceToSurfaceScale(compositeType));
primitives.add(new VKSurfaceToSurfaceTransform(compositeType));
}
// Surface->Sw & Any composite per format
for (VKFormat format : VKFormat.values()) {
primitives.add(new VKSurfaceToSwBlit(format, OPAQUE));
if (format.isTranslucencyCapable()) {
@@ -322,6 +250,44 @@ final class VKBlitLoops {
}
}
@FunctionalInterface
interface VKStageSurfaceFactory {
BufferedImage create(int width, int height);
}
final class VKStageSurface implements AutoCloseable {
private final VKStageSurfaceFactory factory;
private WeakReference<SurfaceData> cachedSD;
private SurfaceData currentSD;
VKStageSurface(VKStageSurfaceFactory factory) {
this.factory = factory;
}
SurfaceData acquire(int width, int height) {
if(currentSD != null) throw new IllegalStateException("Already acquired, recursive blit?");
if (cachedSD != null) {
currentSD = cachedSD.get();
if (currentSD != null) {
Rectangle b = currentSD.getBounds();
if (b.width < width || b.height < height) currentSD = null;
} else cachedSD = null;
}
if (currentSD == null) {
BufferedImage bi = factory.create(width, height);
currentSD = SurfaceData.getPrimarySurfaceData(bi);
cachedSD = new WeakReference<>(currentSD);
}
return currentSD;
}
@Override
public void close() {
currentSD = null;
}
}
final class VKSurfaceToSurfaceBlit extends Blit {
VKSurfaceToSurfaceBlit(CompositeType compositeType) {
@@ -385,53 +351,17 @@ final class VKSurfaceToSurfaceTransform extends TransformBlit {
final class VKSurfaceToSwBlit extends Blit {
private final VKFormat format;
private final int transparency;
private final VKStageSurface stageSurface;
private final SurfaceType bufferedSurfaceType;
private WeakReference<SurfaceData> srcTmp;
VKSurfaceToSwBlit(VKFormat format, int transparency) {
// TODO Support for any composite via staged blit?
// This way we could do one intermediate blit instead of two.
super(format.getSurfaceType(transparency), CompositeType.SrcNoEa, SurfaceType.Any);
this.format = format;
this.transparency = transparency;
stageSurface = new VKStageSurface((w, h) -> format.createCompatibleImage(w, h, transparency));
bufferedSurfaceType = format.getFormatModel(transparency).getSurfaceType();
}
private void stagedBlit(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
int sx, int sy, int dx, int dy,
int w, int h) {
SurfaceData tmp = null;
if (srcTmp != null) {
// Use cached intermediate surface, if available.
tmp = srcTmp.get();
if (tmp != null) {
Rectangle b = tmp.getBounds();
if (b.width < w || b.height < h) tmp = null;
}
}
if (tmp == null) {
BufferedImage bi = format.createCompatibleImage(w, h, transparency);
tmp = SurfaceData.getPrimarySurfaceData(bi);
if (tmp.getSurfaceType() != bufferedSurfaceType) throw new RuntimeException("Incompatible surface type");
srcTmp = null;
}
// Blit from Vulkan to intermediate SW image.
Blit(src, tmp, null, null, sx, sy, 0, 0, w, h);
// Copy intermediate SW to destination SW using complex clip.
Blit performop = Blit.getFromCache(tmp.getSurfaceType(), CompositeType.SrcNoEa, dst.getSurfaceType());
performop.Blit(tmp, dst, comp, clip, 0, 0, dx, dy, w, h);
if (srcTmp == null) {
// Cache the intermediate surface.
srcTmp = new WeakReference<>(tmp);
}
}
@Override
public void Blit(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
@@ -453,7 +383,14 @@ final class VKSurfaceToSwBlit extends Blit {
}
if ((clip != null && !clip.isRectangular()) || dst.getSurfaceType() != bufferedSurfaceType) {
stagedBlit(src, dst, comp, clip, sx, sy, dx, dy, w, h);
try (stageSurface) {
SurfaceData stage = stageSurface.acquire(w, h);
// Blit from Vulkan to intermediate SW image.
Blit(src, stage, null, null, sx, sy, 0, 0, w, h);
// Copy intermediate SW to destination SW using complex clip.
Blit op = Blit.getFromCache(stage.getSurfaceType(), CompositeType.SrcNoEa, dst.getSurfaceType());
op.Blit(stage, dst, comp, clip, 0, 0, dx, dy, w, h);
}
return;
}
@@ -473,7 +410,7 @@ final class VKSurfaceToSwBlit extends Blit {
buf.putInt(sx).putInt(sy);
buf.putInt(dx).putInt(dy);
buf.putInt(w).putInt(h);
buf.putInt(format.getValue(dst.getTransparency()));
buf.putInt(0 /*unused*/);
buf.putLong(src.getNativeOps());
buf.putLong(dst.getNativeOps());
@@ -485,39 +422,70 @@ final class VKSurfaceToSwBlit extends Blit {
}
}
final class VKSwToSurfaceBlitContext {
// TODO switch to TYPE_INT_ARGB when blit shader is fixed to handle straight alpha?
// Don't use pre-multiplied alpha to preserve color values whenever possible.
private final VKStageSurface stage4Byte = new VKStageSurface((w, h) -> new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE));
VKStageSurface getStage(SurfaceData src, VKSurfaceData dst) {
// We assume that 4 byte sampled format is always supported.
return stage4Byte;
}
/**
* Encode src surface type for native blit.
* Return -1 if the surface type is not supported.
* All stage surfaces from getStage() must always be supported.
*/
int encodeSrcType(SurfaceData src) {
// TODO Needs to be implemented.
// Currently, all blits are performed via a staged surface.
if (src.getSurfaceType() == SurfaceType.IntArgbPre) return 0;
return -1;
}
}
class VKSwToSurfaceBlit extends Blit {
private int typeval;
private final VKSwToSurfaceBlitContext blitContext;
VKSwToSurfaceBlit(SurfaceType srcType, int typeval) {
super(srcType,
CompositeType.AnyAlpha,
VKSurfaceData.VKSurface);
this.typeval = typeval;
VKSwToSurfaceBlit(CompositeType compositeType, VKSwToSurfaceBlitContext blitContext) {
super(SurfaceType.Any, compositeType, VKSurfaceData.VKSurface);
this.blitContext = blitContext;
}
public void Blit(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
int sx, int sy, int dx, int dy, int w, int h)
{
int sx, int sy, int dx, int dy, int w, int h) {
int srcType = blitContext.encodeSrcType(src);
if (srcType == -1) {
try (VKStageSurface stageSurface = blitContext.getStage(src, (VKSurfaceData) dst)) {
SurfaceData stage = stageSurface.acquire(w, h);
// Blit from src to intermediate SW image.
Blit op = Blit.getFromCache(src.getSurfaceType(), CompositeType.SrcNoEa, stage.getSurfaceType());
op.Blit(src, stage, AlphaComposite.Src, null, sx, sy, 0, 0, w, h);
// Copy intermediate SW to Vulkan surface.
Blit(stage, dst, comp, clip, 0, 0, dx, dy, w, h);
}
return;
}
VKBlitLoops.Blit(src, dst,
comp, clip, null,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
sx, sy, sx+w, sy+h,
dx, dy, dx+w, dy+h,
typeval, false);
srcType, false);
}
}
class VKSwToSurfaceScale extends ScaledBlit {
private int typeval;
private final VKSwToSurfaceBlitContext blitContext;
VKSwToSurfaceScale(SurfaceType srcType, int typeval) {
super(srcType,
CompositeType.AnyAlpha,
VKSurfaceData.VKSurface);
this.typeval = typeval;
VKSwToSurfaceScale(CompositeType compositeType, VKSwToSurfaceBlitContext blitContext) {
super(SurfaceType.Any, compositeType, VKSurfaceData.VKSurface);
this.blitContext = blitContext;
}
public void Scale(SurfaceData src, SurfaceData dst,
@@ -525,222 +493,58 @@ class VKSwToSurfaceScale extends ScaledBlit {
int sx1, int sy1,
int sx2, int sy2,
double dx1, double dy1,
double dx2, double dy2)
{
double dx2, double dy2) {
int srcType = blitContext.encodeSrcType(src);
if (srcType == -1) {
try (VKStageSurface stageSurface = blitContext.getStage(src, (VKSurfaceData) dst)) {
int w = sx2-sx1, h = sy2-sy1;
SurfaceData stage = stageSurface.acquire(w, h);
// Blit from src to intermediate SW image.
Blit op = Blit.getFromCache(src.getSurfaceType(), CompositeType.SrcNoEa, stage.getSurfaceType());
op.Blit(src, stage, AlphaComposite.Src, null, sx1, sy1, 0, 0, w, h);
// Copy intermediate SW to Vulkan surface.
Scale(stage, dst, comp, clip, 0, 0, w, h, dx1, dy1, dx2, dy2);
}
return;
}
VKBlitLoops.Blit(src, dst,
comp, clip, null,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
sx1, sy1, sx2, sy2,
dx1, dy1, dx2, dy2,
typeval, false);
srcType, false);
}
}
class VKSwToSurfaceTransform extends TransformBlit {
private int typeval;
private final VKSwToSurfaceBlitContext blitContext;
VKSwToSurfaceTransform(SurfaceType srcType, int typeval) {
super(srcType,
CompositeType.AnyAlpha,
VKSurfaceData.VKSurface);
this.typeval = typeval;
VKSwToSurfaceTransform(CompositeType compositeType, VKSwToSurfaceBlitContext blitContext) {
super(SurfaceType.Any, compositeType, VKSurfaceData.VKSurface);
this.blitContext = blitContext;
}
public void Transform(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
AffineTransform at, int hint,
int sx, int sy, int dx, int dy, int w, int h)
{
int sx, int sy, int dx, int dy, int w, int h) {
int srcType = blitContext.encodeSrcType(src);
if (srcType == -1) {
try (VKStageSurface stageSurface = blitContext.getStage(src, (VKSurfaceData) dst)) {
SurfaceData stage = stageSurface.acquire(w, h);
// Blit from src to intermediate SW image.
Blit op = Blit.getFromCache(src.getSurfaceType(), CompositeType.SrcNoEa, stage.getSurfaceType());
op.Blit(src, stage, AlphaComposite.Src, null, sx, sy, 0, 0, w, h);
// Copy intermediate SW to Vulkan surface.
Transform(stage, dst, comp, clip, at, hint, 0, 0, dx, dy, w, h);
}
return;
}
VKBlitLoops.Blit(src, dst,
comp, clip, at, hint,
sx, sy, sx+w, sy+h,
dx, dy, dx+w, dy+h,
typeval, false);
}
}
class VKSwToTextureBlit extends Blit {
private int typeval;
VKSwToTextureBlit(SurfaceType srcType, int typeval) {
super(srcType,
CompositeType.SrcNoEa,
VKSurfaceData.VKTexture);
this.typeval = typeval;
}
public void Blit(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
int sx, int sy, int dx, int dy, int w, int h)
{
VKBlitLoops.Blit(src, dst,
comp, clip, null,
AffineTransformOp.TYPE_NEAREST_NEIGHBOR,
sx, sy, sx+w, sy+h,
dx, dy, dx+w, dy+h,
typeval, true);
}
}
/**
* This general Blit implementation converts any source surface to an
* intermediate IntArgbPre surface, and then uses the more specific
* IntArgbPre->VKSurface/Texture loop to get the intermediate
* (premultiplied) surface down to Metal using simple blit.
*/
class VKGeneralBlit extends Blit {
private final Blit performop;
private WeakReference<SurfaceData> srcTmp;
VKGeneralBlit(SurfaceType dstType,
CompositeType compType,
Blit performop)
{
super(SurfaceType.Any, compType, dstType);
this.performop = performop;
}
public synchronized void Blit(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
int sx, int sy, int dx, int dy,
int w, int h)
{
Blit convertsrc = Blit.getFromCache(src.getSurfaceType(),
CompositeType.SrcNoEa,
SurfaceType.IntArgbPre);
SurfaceData cachedSrc = null;
if (srcTmp != null) {
// use cached intermediate surface, if available
cachedSrc = srcTmp.get();
}
// convert source to IntArgbPre
src = convertFrom(convertsrc, src, sx, sy, w, h,
cachedSrc, BufferedImage.TYPE_INT_ARGB_PRE);
// copy IntArgbPre intermediate surface to Metal surface
performop.Blit(src, dst, comp, clip,
0, 0, dx, dy, w, h);
if (src != cachedSrc) {
// cache the intermediate surface
srcTmp = new WeakReference<>(src);
}
}
}
/**
* This general TransformedBlit implementation converts any source surface to an
* intermediate IntArgbPre surface, and then uses the more specific
* IntArgbPre->VKSurface/Texture loop to get the intermediate
* (premultiplied) surface down to Metal using simple transformBlit.
*/
final class VKGeneralTransformedBlit extends TransformBlit {
private final TransformBlit performop;
private WeakReference<SurfaceData> srcTmp;
VKGeneralTransformedBlit(final TransformBlit performop) {
super(SurfaceType.Any, CompositeType.AnyAlpha,
VKSurfaceData.VKSurface);
this.performop = performop;
}
@Override
public synchronized void Transform(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
AffineTransform at, int hint, int srcx,
int srcy, int dstx, int dsty, int width,
int height){
Blit convertsrc = Blit.getFromCache(src.getSurfaceType(),
CompositeType.SrcNoEa,
SurfaceType.IntArgbPre);
// use cached intermediate surface, if available
final SurfaceData cachedSrc = srcTmp != null ? srcTmp.get() : null;
// convert source to IntArgbPre
src = convertFrom(convertsrc, src, srcx, srcy, width, height, cachedSrc,
BufferedImage.TYPE_INT_ARGB_PRE);
// transform IntArgbPre intermediate surface to Metal surface
performop.Transform(src, dst, comp, clip, at, hint, 0, 0, dstx, dsty,
width, height);
if (src != cachedSrc) {
// cache the intermediate surface
srcTmp = new WeakReference<>(src);
}
}
}
/**
* This general VKAnyCompositeBlit implementation can convert any source/target
* surface to an intermediate surface using convertsrc/convertdst loops, applies
* necessary composite operation, and then uses convertresult loop to get the
* intermediate surface down to Metal.
*/
final class VKAnyCompositeBlit extends Blit {
private WeakReference<SurfaceData> dstTmp;
private WeakReference<SurfaceData> srcTmp;
private final Blit convertsrc;
private final Blit convertdst;
private final Blit convertresult;
VKAnyCompositeBlit(SurfaceType srctype, Blit convertsrc, Blit convertdst,
Blit convertresult) {
super(srctype, CompositeType.Any, VKSurfaceData.VKSurface);
this.convertsrc = convertsrc;
this.convertdst = convertdst;
this.convertresult = convertresult;
}
public synchronized void Blit(SurfaceData src, SurfaceData dst,
Composite comp, Region clip,
int sx, int sy, int dx, int dy,
int w, int h)
{
if (convertsrc != null) {
SurfaceData cachedSrc = null;
if (srcTmp != null) {
// use cached intermediate surface, if available
cachedSrc = srcTmp.get();
}
// convert source to IntArgbPre
src = convertFrom(convertsrc, src, sx, sy, w, h, cachedSrc,
BufferedImage.TYPE_INT_ARGB_PRE);
if (src != cachedSrc) {
// cache the intermediate surface
srcTmp = new WeakReference<>(src);
}
}
SurfaceData cachedDst = null;
if (dstTmp != null) {
// use cached intermediate surface, if available
cachedDst = dstTmp.get();
}
// convert destination to IntArgbPre
SurfaceData dstBuffer = convertFrom(convertdst, dst, dx, dy, w, h,
cachedDst, BufferedImage.TYPE_INT_ARGB_PRE);
Region bufferClip =
clip == null ? null : clip.getTranslatedRegion(-dx, -dy);
Blit performop = Blit.getFromCache(src.getSurfaceType(),
CompositeType.Any, dstBuffer.getSurfaceType());
performop.Blit(src, dstBuffer, comp, bufferClip, sx, sy, 0, 0, w, h);
if (dstBuffer != cachedDst) {
// cache the intermediate surface
dstTmp = new WeakReference<>(dstBuffer);
}
// now blit the buffer back to the destination
convertresult.Blit(dstBuffer, dst, AlphaComposite.Src, clip, 0, 0, dx,
dy, w, h);
srcType, false);
}
}

View File

@@ -7,6 +7,4 @@ layout(location = 0) out vec4 out_Color;
void main() {
// TODO consider in/out alpha type.
out_Color = texture(u_TexSampler, in_TexCoord);
// TODO: Temporary fix of unexpected transparency with blit operations
out_Color.a = 1.0;
}

View File

@@ -53,11 +53,6 @@ static void VKBlitSwToTextureViaPooledTexture(VKRenderingContext* context,
const int dw = dx2 - dx1;
const int dh = dy2 - dy1;
if (dw < sw || dh < sh) {
J2dTraceLn4(J2D_TRACE_ERROR, "VKBlitSwToTextureViaPooledTexture: dest size: (%d, %d) less than source size: (%d, %d)", dw, dh, sw, sh);
return;
}
ARRAY(VKTxVertex) vertices = ARRAY_ALLOC(VKTxVertex, 4);
/*
* (p1)---------(p2)

View File

@@ -32,7 +32,6 @@ import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
/*
* @test
@@ -47,10 +46,8 @@ import java.util.Arrays;
public class VulkanBlitTest {
static boolean SUPPRESS_ALPHA_VALIDATION = false; // TODO this is temporary.
final static int W = 256*3;
final static int H = 600;
final static int[] FILL_SCANLINE = new int[W];
final static Color FILL_COLOR = new Color(0xffa1babe);
final static Rectangle[] CLIP_RECTS = new Rectangle[] {
new Rectangle(W*1/12, H*0/10, W/6, H/10+1),
@@ -65,7 +62,6 @@ public class VulkanBlitTest {
};
final static Area COMPLEX_CLIP = new Area();
static {
Arrays.fill(FILL_SCANLINE, 0xffa1babe);
for (Rectangle clip : CLIP_RECTS) COMPLEX_CLIP.add(new Area(clip));
}
@@ -144,63 +140,71 @@ public class VulkanBlitTest {
validate(image, W*j/256+1, H*7/10, new Color(j, j, j), "opaque gradient stripe " + j, 1);
}
for (int j = 0; j < 256; j++) {
if (SUPPRESS_ALPHA_VALIDATION) break;
Color expected = new Color(j, j, j, hasAlpha ? j : 255);
validate(image, W*j/256+1, H*9/10, expected, "alpha gradient stripe " + j, 12);
}
}
static void testSurfaceToSwBlit(BufferedImage bi, VolatileImage image, String prefix, boolean hasAlpha) throws IOException {
// Fill the buffered image with plain color.
bi.setRGB(0, 0, W, H, FILL_SCANLINE, 0, 0);
{ // Blit into software image.
Graphics2D g = bi.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(image, 0, 0, null);
g.dispose();
}
ImageIO.write(bi, "PNG", new File(prefix + "no-clip.png"));
try {
validate(bi, hasAlpha);
} catch (Throwable t) {
throw new Error(prefix + "no-clip", t);
}
static void testBlit(Image src, Image dst, String prefix, boolean hasAlpha) throws IOException {
BufferedImage validationImage;
// Fill the buffered image with plain color.
bi.setRGB(0, 0, W, H, FILL_SCANLINE, 0, 0);
{ // Blit again with rect clips.
Graphics2D g = bi.createGraphics();
{
Graphics2D g = (Graphics2D) dst.getGraphics();
g.setComposite(AlphaComposite.Src);
g.setColor(FILL_COLOR);
g.fillRect(0, 0, W, H);
for (Rectangle clip : CLIP_RECTS) {
g.setClip(clip);
g.drawImage(image, 0, 0, null);
g.drawImage(src, 0, 0, null);
}
g.dispose();
}
ImageIO.write(bi, "PNG", new File(prefix + "rect-clip.png"));
validationImage = dst instanceof BufferedImage ? (BufferedImage) dst : ((VolatileImage) dst).getSnapshot();
ImageIO.write(validationImage, "PNG", new File(prefix + "rect-clip.png"));
try {
validate(bi, hasAlpha);
validateClip(bi);
validate(validationImage, hasAlpha);
validateClip(validationImage);
} catch (Throwable t) {
throw new Error(prefix + "rect-clip", t);
}
// Fill the buffered image with plain color.
bi.setRGB(0, 0, W, H, FILL_SCANLINE, 0, 0);
{ // Blit again with a complex clip.
Graphics2D g = bi.createGraphics();
{
Graphics2D g = (Graphics2D) dst.getGraphics();
g.setComposite(AlphaComposite.Src);
g.setColor(FILL_COLOR);
g.fillRect(0, 0, W, H);
g.setClip(COMPLEX_CLIP);
g.drawImage(image, 0, 0, null);
g.drawImage(src, 0, 0, null);
g.dispose();
}
ImageIO.write(bi, "PNG", new File(prefix + "complex-clip.png"));
validationImage = dst instanceof BufferedImage ? (BufferedImage) dst : ((VolatileImage) dst).getSnapshot();
ImageIO.write(validationImage, "PNG", new File(prefix + "complex-clip.png"));
try {
validate(bi, hasAlpha);
validateClip(bi);
validate(validationImage, hasAlpha);
validateClip(validationImage);
} catch (Throwable t) {
throw new Error(prefix + "complex-clip", t);
}
{
Graphics2D g = (Graphics2D) dst.getGraphics();
g.setComposite(AlphaComposite.Src);
g.setColor(FILL_COLOR);
g.fillRect(0, 0, W, H);
g.drawImage(src, 0, 0, null);
g.dispose();
}
validationImage = dst instanceof BufferedImage ? (BufferedImage) dst : ((VolatileImage) dst).getSnapshot();
ImageIO.write(validationImage, "PNG", new File(prefix + "no-clip.png"));
try {
validate(validationImage, hasAlpha);
try {
validateClip(validationImage);
throw new Error("Clip validation succeeded");
} catch (Throwable ignore) {}
} catch (Throwable t) {
throw new Error(prefix + "no-clip", t);
}
}
static void test(VKGraphicsConfig vkgc, boolean hasAlpha) throws IOException {
@@ -222,46 +226,44 @@ public class VulkanBlitTest {
throw new Error(prefix + "snapshot", t);
}
// Repeat blit into the same snapshot image.
testSurfaceToSwBlit(bi, image, prefix + "snapshot-", hasAlpha);
// Create another Vulkan image.
VolatileImage check = config.createCompatibleVolatileImage(W, H, transparency);
if (check.validate(config) == VolatileImage.IMAGE_INCOMPATIBLE) {
throw new Error("Image validation failed");
}
// Repeat blit to/from the same snapshot image.
testBlit(image, bi, prefix + "snapshot, surface-sw, ", hasAlpha);
testBlit(bi, check, prefix + "snapshot, sw-surface, ", hasAlpha);
// Repeat with generic buffered image formats.
bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_RGB);
testSurfaceToSwBlit(bi, image, prefix + "INT_RGB, ", false);
testBlit(image, bi, prefix + "INT_RGB, surface-sw, ", false);
testBlit(bi, check, prefix + "INT_RGB, sw-surface, ", false);
bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB);
testSurfaceToSwBlit(bi, image, prefix + "INT_ARGB, ", hasAlpha);
testBlit(image, bi, prefix + "INT_ARGB, surface-sw, ", hasAlpha);
testBlit(bi, check, prefix + "INT_ARGB, sw-surface, ", hasAlpha);
bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB_PRE);
testSurfaceToSwBlit(bi, image, prefix + "INT_ARGB_PRE, ", hasAlpha);
testBlit(image, bi, prefix + "INT_ARGB_PRE, surface-sw, ", hasAlpha);
testBlit(bi, check, prefix + "INT_ARGB_PRE, sw-surface, ", hasAlpha);
bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_BGR);
testSurfaceToSwBlit(bi, image, prefix + "INT_BGR, ", false);
testBlit(image, bi, prefix + "INT_BGR, surface-sw, ", false);
testBlit(bi, check, prefix + "INT_BGR, sw-surface, ", false);
bi = new BufferedImage(W, H, BufferedImage.TYPE_3BYTE_BGR);
testSurfaceToSwBlit(bi, image, prefix + "3BYTE_BGR, ", false);
testBlit(image, bi, prefix + "3BYTE_BGR, surface-sw, ", false);
testBlit(bi, check, prefix + "3BYTE_BGR, sw-surface, ", false);
bi = new BufferedImage(W, H, BufferedImage.TYPE_4BYTE_ABGR);
testSurfaceToSwBlit(bi, image, prefix + "4BYTE_ABGR, ", hasAlpha);
testBlit(image, bi, prefix + "4BYTE_ABGR, surface-sw, ", hasAlpha);
testBlit(bi, check, prefix + "4BYTE_ABGR, sw-surface, ", hasAlpha);
bi = new BufferedImage(W, H, BufferedImage.TYPE_4BYTE_ABGR_PRE);
testSurfaceToSwBlit(bi, image, prefix + "4BYTE_ABGR_PRE, ", hasAlpha);
testBlit(image, bi, prefix + "4BYTE_ABGR_PRE, surface-sw, ", hasAlpha);
testBlit(bi, check, prefix + "4BYTE_ABGR_PRE, sw-surface, ", hasAlpha);
// Blit into another Vulkan image.
hasAlpha = false; // TODO our blit currently ignores alpha, remove when fixed.
SUPPRESS_ALPHA_VALIDATION = true; // TODO same.
VolatileImage anotherImage = config.createCompatibleVolatileImage(W, H, transparency);
if (anotherImage.validate(config) == VolatileImage.IMAGE_INCOMPATIBLE) {
throw new Error("Image validation failed");
}
{
Graphics2D g = anotherImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
}
if (anotherImage.contentsLost()) throw new Error("Image contents lost");
// Take a snapshot (blit into Sw) and validate.
bi = anotherImage.getSnapshot();
ImageIO.write(bi, "PNG", new File(prefix + "another-snapshot.png"));
try {
validate(bi, hasAlpha);
} catch (Throwable t) {
throw new Error(prefix + "another-snapshot", t);
}
testBlit(image, check, prefix + "surface-surface, ", hasAlpha);
if (image.contentsLost()) throw new Error("Image contents lost");
if (check.contentsLost()) throw new Error("Check contents lost");
}
public static void main(String[] args) throws IOException {
@@ -281,9 +283,6 @@ public class VulkanBlitTest {
else if (args.length > 0 && args[0].equals("OPAQUE")) hasAlpha = false;
else throw new Error("Usage: VulkanBlitTest <TRANSLUCENT|OPAQUE>");
// TODO We don't have proper support for alpha rendering into OPAQUE surface atm.
if (!hasAlpha) SUPPRESS_ALPHA_VALIDATION = true;
VKEnv.getDevices().flatMap(VKGPU::getOffscreenGraphicsConfigs).forEach(gc -> {
System.out.println("Testing " + gc);
try {