Skip to content

Commit

Permalink
JBR-7990 Vulkan: Robot pixel grabbing for Vulkan surfaces
Browse files Browse the repository at this point in the history
Implemented grabbing pixels via partly supported SurfaceToSwBlit, fixed multi-monitor scenario
  • Loading branch information
avu committed Jan 15, 2025
1 parent 3318b7e commit c36d636
Show file tree
Hide file tree
Showing 12 changed files with 569 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,7 @@ static jboolean VK_InitDevice(VKDevice* device) {
DEVICE_PROC(vkInvalidateMappedMemoryRanges);
DEVICE_PROC(vkCmdPushConstants);
DEVICE_PROC(vkCmdCopyBufferToImage);
DEVICE_PROC(vkCmdCopyImageToBuffer);

device->vkGetDeviceQueue(device->handle, device->queueFamily, 0, &device->queue);
if (device->queue == NULL) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ struct VKDevice {
PFN_vkInvalidateMappedMemoryRanges vkInvalidateMappedMemoryRanges;
PFN_vkCmdPushConstants vkCmdPushConstants;
PFN_vkCmdCopyBufferToImage vkCmdCopyBufferToImage;
PFN_vkCmdCopyImageToBuffer vkCmdCopyImageToBuffer;
};

struct VKGraphicsEnvironment {
Expand Down
118 changes: 118 additions & 0 deletions src/java.desktop/share/native/common/java2d/vulkan/VKBlitLoops.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "VKBlitLoops.h"
#include "VKSurfaceData.h"
#include "VKRenderer.h"
#include "GraphicsPrimitiveMgr.h"


#include "Trace.h"
Expand Down Expand Up @@ -316,4 +317,121 @@ void VKBlitLoops_Blit(JNIEnv *env,
SurfaceData_InvokeRelease(env, srcOps, &srcInfo);
}
SurfaceData_InvokeUnlock(env, srcOps, &srcInfo);
}

/**
* Specialized blit method for copying a native Vulkan "Surface" to a system
* memory ("Sw") surface.
*/
void
VKBlitLoops_SurfaceToSwBlit(JNIEnv *env, VKRenderingContext* context,
jlong pSrcOps, jlong pDstOps, jint dsttype,
jint srcx, jint srcy, jint dstx, jint dsty,
jint width, jint height)
{
VKSDOps *srcOps = (VKSDOps *)jlong_to_ptr(pSrcOps);
SurfaceDataOps *dstOps = (SurfaceDataOps *)jlong_to_ptr(pDstOps);
SurfaceDataRasInfo srcInfo, dstInfo;

J2dTraceLn6(J2D_TRACE_INFO, "VKBlitLoops_SurfaceToSwBlit: (%d %d %d %d) -> (%d %d)",
srcx, srcy, width, height, dstx, dsty);

if (width <= 0 || height <= 0) {
J2dTraceLn(J2D_TRACE_WARNING,
"VKBlitLoops_SurfaceToSwBlit: dimensions are non-positive");
return;
}

RETURN_IF_NULL(srcOps);
RETURN_IF_NULL(dstOps);
RETURN_IF_NULL(context);

VKDevice* device = srcOps->device;
VKImage* image = srcOps->image;

if (image == NULL || device == NULL) {
J2dRlsTraceLn2(J2D_TRACE_ERROR,
"VKBlitLoops_SurfaceToSwBlit: image(%p) or device(%p) is NULL", image, device)
return;
}

srcInfo.bounds.x1 = srcx;
srcInfo.bounds.y1 = srcy;
srcInfo.bounds.x2 = srcx + width;
srcInfo.bounds.y2 = srcy + height;
dstInfo.bounds.x1 = dstx;
dstInfo.bounds.y1 = dsty;
dstInfo.bounds.x2 = dstx + width;
dstInfo.bounds.y2 = dsty + height;

SurfaceData_IntersectBoundsXYXY(&srcInfo.bounds,
0, 0, srcOps->image->extent.width, srcOps->image->extent.height);
SurfaceData_IntersectBlitBounds(&dstInfo.bounds, &srcInfo.bounds,
srcx - dstx, srcy - dsty);

if (dstOps->Lock(env, dstOps, &dstInfo, SD_LOCK_WRITE) != SD_SUCCESS) {
J2dTraceLn(J2D_TRACE_WARNING,
"VKBlitLoops_SurfaceToSwBlit: could not acquire dst lock");
return;
}

if (srcInfo.bounds.x2 > srcInfo.bounds.x1 &&
srcInfo.bounds.y2 > srcInfo.bounds.y1)
{
dstOps->GetRasInfo(env, dstOps, &dstInfo);
if (dstInfo.rasBase) {
void *pDst = dstInfo.rasBase;
srcx = srcInfo.bounds.x1;
srcy = srcInfo.bounds.y1;
dstx = dstInfo.bounds.x1;
dsty = dstInfo.bounds.y1;
width = srcInfo.bounds.x2 - srcInfo.bounds.x1;
height = srcInfo.bounds.y2 - srcInfo.bounds.y1;
jsize bufferSizeInPixels = width * height;

pDst = PtrAddBytes(pDst, dstx * dstInfo.pixelStride);
pDst = PtrPixelsRow(pDst, dsty, dstInfo.scanStride);

VKRenderer_FlushRenderPass(srcOps);
VkCommandBuffer cb = VKRenderer_Record(device->renderer);

VKBuffer* buffer = VKBuffer_Create(device, bufferSizeInPixels * sizeof(jint),
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

VkBufferImageCopy region = {
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource= {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1
},
.imageOffset = {srcx, srcy, 0},
.imageExtent = {width, height, 1}
};

device->vkCmdCopyImageToBuffer(cb, image->handle, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
buffer->handle,
1, &region);
VKRenderer_Flush(device->renderer);
VKRenderer_Sync(device->renderer);
void* pixelData;
VK_IF_ERROR(device->vkMapMemory(device->handle, buffer->range.memory,
0, VK_WHOLE_SIZE, 0, &pixelData))
{
J2dRlsTraceLn(J2D_TRACE_ERROR, "VKBlitLoops_SurfaceToSwBlit: could not map buffer memory");
VKBuffer_Destroy(device, buffer);
return;
}
memcpy(pDst, pixelData, bufferSizeInPixels * sizeof(jint));
device->vkUnmapMemory(device->handle, buffer->range.memory);
VKBuffer_Destroy(device, buffer);
}
SurfaceData_InvokeRelease(env, dstOps, &dstInfo);
}
SurfaceData_InvokeUnlock(env, dstOps, &dstInfo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,10 @@ void VKBlitLoops_Blit(JNIEnv *env,
jdouble dx1, jdouble dy1,
jdouble dx2, jdouble dy2);

void
VKBlitLoops_SurfaceToSwBlit(JNIEnv *env, VKRenderingContext* context,
jlong pSrcOps, jlong pDstOps, jint dsttype,
jint srcx, jint srcy, jint dstx, jint dsty,
jint width, jint height);

#endif /* VKBlitLoops_h_Included */
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ JNIEXPORT void JNICALL Java_sun_java2d_vulkan_VKRenderQueue_flushBuffer
jlong pSrc = NEXT_LONG(b);
jlong pDst = NEXT_LONG(b);
J2dRlsTraceLn(J2D_TRACE_VERBOSE, "VKRenderQueue_flushBuffer: SURFACE_TO_SW_BLIT");
VKBlitLoops_SurfaceToSwBlit(env, &context, pSrc, pDst, dsttype, sx, sy, dx, dy, w, h);
}
break;
case sun_java2d_pipe_BufferedOpCodes_MASK_FILL:
Expand Down
16 changes: 10 additions & 6 deletions src/java.desktop/unix/classes/sun/awt/wl/WLRobotPeer.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
package sun.awt.wl;

import sun.java2d.SurfaceData;
import sun.java2d.wl.WLSMSurfaceData;
import sun.java2d.wl.WLPixelGrabberExt;

import java.awt.*;
import java.awt.peer.RobotPeer;
Expand Down Expand Up @@ -121,7 +121,11 @@ public int getRGBPixel(int x, int y) {
} else {
// Can get pixels from the singular window's surface data,
// not necessarily the true value that the user observes.
return getRGBPixelsOfSingularWindow(bounds);
Rectangle deviceBounds = wgc.getDefaultTransform().createTransformedShape(wgc.getBounds()).getBounds();

Rectangle grabBounds = new Rectangle(bounds.x - deviceBounds.x, bounds.y - deviceBounds.y,
bounds.width, bounds.height);
return getRGBPixelsOfSingularWindow(grabBounds);
}
}

Expand All @@ -130,7 +134,7 @@ private int getRGBPixelOfSingularWindow(int x, int y) {
WLToolkit.awtLock();
try {
checkPeerForPixelGrab(peer);
return SurfaceData.convertTo(WLSMSurfaceData.class, peer.surfaceData).getRGBPixelAt(x, y);
return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelAt(x, y);
} finally {
WLToolkit.awtUnlock();
}
Expand All @@ -141,7 +145,7 @@ private int getRGBPixelOfSingularWindow(int x, int y) {
WLToolkit.awtLock();
try {
checkPeerForPixelGrab(peer);
return SurfaceData.convertTo(WLSMSurfaceData.class, peer.surfaceData).getRGBPixelsAt(bounds);
return SurfaceData.convertTo(WLPixelGrabberExt.class, peer.surfaceData).getRGBPixelsAt(bounds);
} finally {
WLToolkit.awtUnlock();
}
Expand All @@ -151,8 +155,8 @@ private static void checkPeerForPixelGrab(WLComponentPeer peer) {
if (!peer.hasSurface()) {
throw new UnsupportedOperationException("The window has no backing buffer to read pixels from");
}
if (! (peer.surfaceData instanceof WLSMSurfaceData)) {
throw new UnsupportedOperationException("Reading pixels of a window is only supported for memory-mapped buffers");
if (! (peer.surfaceData instanceof WLPixelGrabberExt)) {
throw new UnsupportedOperationException("WLPixelGrabberExt is required to read pixels from a Wayland surface");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,29 @@

package sun.java2d.vulkan;

import java.awt.AlphaComposite;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import sun.awt.image.BufImgSurfaceData;
import sun.awt.wl.WLComponentPeer;
import sun.java2d.SurfaceData;
import sun.java2d.loops.Blit;
import sun.java2d.loops.CompositeType;
import sun.java2d.loops.SurfaceType;
import sun.java2d.pipe.BufferedContext;
import sun.java2d.pipe.RenderBuffer;
import sun.java2d.wl.WLPixelGrabberExt;
import sun.java2d.wl.WLSurfaceDataExt;
import sun.util.logging.PlatformLogger;

import static sun.java2d.pipe.BufferedOpCodes.FLUSH_BUFFER;
import static sun.java2d.pipe.BufferedOpCodes.CONFIGURE_SURFACE;

public abstract class WLVKSurfaceData extends VKSurfaceData implements WLSurfaceDataExt {
public abstract class WLVKSurfaceData extends VKSurfaceData implements WLSurfaceDataExt, WLPixelGrabberExt {
private static final PlatformLogger log = PlatformLogger.getLogger("sun.java2d.vulkan.WLVKSurfaceData");

protected WLComponentPeer peer;
Expand Down Expand Up @@ -145,6 +151,51 @@ public static WLVKGraphicsConfig getGC(WLComponentPeer peer) {
}
}

public int getRGBPixelAt(int x, int y) {
Rectangle r = peer.getBufferBounds();
if (x < r.x || x >= r.x + r.width || y < r.y || y >= r.y + r.height) {
throw new ArrayIndexOutOfBoundsException("x,y outside of buffer bounds");
}

BufferedImage resImg = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
SurfaceData resData = BufImgSurfaceData.createData(resImg);

Blit blit = Blit.getFromCache(getSurfaceType(), CompositeType.SrcNoEa,
resData.getSurfaceType());
blit.Blit(this, resData, AlphaComposite.Src, null,
x, y, 0, 0, 1, 1);

return resImg.getRGB(0, 0);
}

public int [] getRGBPixelsAt(Rectangle bounds) {
Rectangle r = peer.getBufferBounds();

if ((long)bounds.width * (long)bounds.height > Integer.MAX_VALUE) {
throw new IndexOutOfBoundsException("Dimensions (width=" + bounds.width +
" height=" + bounds.height + ") are too large");
}

Rectangle b = bounds.intersection(r);

if (b.isEmpty()) {
throw new IndexOutOfBoundsException("Requested bounds are outside of surface bounds");
}

BufferedImage resImg = new BufferedImage(b.width, b.height, BufferedImage.TYPE_INT_ARGB);
SurfaceData resData = BufImgSurfaceData.createData(resImg);

Blit blit = Blit.getFromCache(getSurfaceType(), CompositeType.SrcNoEa,
resData.getSurfaceType());
blit.Blit(this, resData, AlphaComposite.Src, null,
b.x, b.y, 0, 0, b.width, b.height);

int [] pixels = new int[b.width * b.height];
resImg.getRGB(0, 0, b.width, b.height, pixels, 0, b.width);
return pixels;
}


public static class WLVKWindowSurfaceData extends WLVKSurfaceData {
public WLVKWindowSurfaceData(WLComponentPeer peer, WLVKGraphicsConfig gc)
{
Expand Down
33 changes: 33 additions & 0 deletions src/java.desktop/unix/classes/sun/java2d/wl/WLPixelGrabberExt.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package sun.java2d.wl;
import java.awt.Rectangle;

public interface WLPixelGrabberExt {
int getRGBPixelAt(int x, int y);
int [] getRGBPixelsAt(Rectangle bounds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
/**
* SurfaceData for shared memory buffers.
*/
public class WLSMSurfaceData extends SurfaceData implements WLSurfaceDataExt {
public class WLSMSurfaceData extends SurfaceData implements WLSurfaceDataExt, WLPixelGrabberExt {
private static final PlatformLogger log = PlatformLogger.getLogger("sun.java2d.wl.WLSMSurfaceData");
private final WLComponentPeer peer;

Expand Down
Loading

0 comments on commit c36d636

Please sign in to comment.