/* * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */ package com.sun.javafx.sg.prism; import java.nio.Buffer; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import java.util.function.UnaryOperator; import com.sun.javafx.geom.Rectangle; import com.sun.prism.Graphics; import com.sun.prism.PixelFormat; import com.sun.prism.ResourceFactory; import com.sun.prism.Texture; public class NGExternalNode extends NGNode { private Texture dsttexture; private BufferData bufferData; private final AtomicReference renderData = new AtomicReference(null); private RenderData rd; // last rendered data private volatile ReentrantLock bufferLock; @Override protected void renderContent(Graphics g) { RenderData curRenderData = renderData.getAndSet(null); if (curRenderData != null) { rd = curRenderData; } if (rd == null) return; int x = rd.bdata.srcbounds.x; int y = rd.bdata.srcbounds.y; int w = rd.bdata.srcbounds.width; int h = rd.bdata.srcbounds.height; if (dsttexture != null) { dsttexture.lock(); if (dsttexture.isSurfaceLost() || (dsttexture.getContentWidth() != w) || (dsttexture.getContentHeight() != h)) { dsttexture.unlock(); dsttexture.dispose(); rd = rd.copyAddDirtyRect(0, 0, w, h); dsttexture = createTexture(g, rd); } } else { dsttexture = createTexture(g, rd); } if (dsttexture == null) { return; } try { if (curRenderData != null) { bufferLock.lock(); try { dsttexture.update(rd.bdata.srcbuffer, PixelFormat.INT_ARGB_PRE, rd.dirtyRect.x, rd.dirtyRect.y, // dst x + rd.dirtyRect.x, y + rd.dirtyRect.y, // src rd.dirtyRect.width, rd.dirtyRect.height, // src rd.bdata.linestride * 4, false); } finally { bufferLock.unlock(); } if (rd.clearTarget) { g.clearQuad(0, 0, rd.bdata.usrwidth, rd.bdata.usrheight); } } g.drawTexture(dsttexture, 0, 0, rd.bdata.usrwidth, rd.bdata.usrheight, // dst 0, 0, w, h); // src } finally { dsttexture.unlock(); } } private Texture createTexture(Graphics g, RenderData rd) { ResourceFactory factory = g.getResourceFactory(); if (!factory.isDeviceReady()) { return null; } Texture txt = factory.createTexture(PixelFormat.INT_ARGB_PRE, Texture.Usage.DYNAMIC, Texture.WrapMode.CLAMP_NOT_NEEDED, rd.bdata.srcbounds.width, rd.bdata.srcbounds.height); if (txt != null) { txt.contentsUseful(); } else { System.err.println("NGExternalNode: failed to create a texture"); } return txt; } public void setLock(ReentrantLock lock) { this.bufferLock = lock; } private static class BufferData { // the source pixel buffer final Buffer srcbuffer; // source buffer line stride final int linestride; // source image physical bounds final Rectangle srcbounds; // source image user space (logical) size final float usrwidth; final float usrheight; // source image scale factor final int scale; BufferData(Buffer srcbuffer, int linestride, int x, int y, int width, int height, float usrWidth, float usrHeight, int scale) { this.srcbuffer = srcbuffer; this.scale = scale; this.linestride = linestride; this.srcbounds = scale(new Rectangle(x, y, width, height)); this.usrwidth = usrWidth; this.usrheight = usrHeight; } Rectangle scale(Rectangle r) { r.x *= this.scale; r.y *= this.scale; r.width *= this.scale; r.height *= this.scale; return r; } BufferData copyWithBounds(int x, int y, int width, int height, float usrWidth, float usrHeight) { return new BufferData(this.srcbuffer, this.linestride, x, y, width, height, usrWidth, usrHeight, this.scale); } } private static class RenderData { final BufferData bdata; final Rectangle dirtyRect; final boolean clearTarget; RenderData(BufferData bdata, int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight, boolean clearTarget) { this(bdata, dirtyX, dirtyY, dirtyWidth, dirtyHeight, clearTarget, true); } RenderData(BufferData bdata, int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight, boolean clearTarget, boolean applyScale) { this.bdata = bdata; Rectangle r = new Rectangle(dirtyX, dirtyY, dirtyWidth, dirtyHeight); this.dirtyRect = applyScale ? bdata.scale(r) : r; this.dirtyRect.intersectWith(bdata.srcbounds); this.clearTarget = clearTarget; } RenderData copyAddDirtyRect(int dirtyX, int dirtyY, int dirtyWidth, int dirtyHeight) { Rectangle r = bdata.scale(new Rectangle(dirtyX, dirtyY, dirtyWidth, dirtyHeight)); r.add(this.dirtyRect); return new RenderData(this.bdata, r.x, r.y, r.width, r.height, this.clearTarget, false); } } public void setImageBuffer(Buffer buffer, int x, int y, int width, int height, float usrWidth, float usrHeight, int linestride, int scale) { bufferData = new BufferData(buffer, linestride, x, y, width, height, usrWidth, usrHeight, scale); renderData.set(new RenderData(bufferData, x, y, width, height, true)); } public void setImageBounds(final int x, final int y, final int width, final int height, final float usrWidth, final float usrHeight) { final boolean shrinked = width < bufferData.usrwidth || height < bufferData.usrheight; bufferData = bufferData.copyWithBounds(x, y, width, height, usrWidth, usrHeight); renderData.updateAndGet(prev -> { boolean clearTarget = (prev != null ? prev.clearTarget : false); return new RenderData(bufferData, x, y, width, height, clearTarget | shrinked); }); } public void repaintDirtyRegion(final int dirtyX, final int dirtyY, final int dirtyWidth, final int dirtyHeight) { renderData.updateAndGet(prev -> { if (prev != null) { return prev.copyAddDirtyRect(dirtyX, dirtyY, dirtyWidth, dirtyHeight); } else { return new RenderData(bufferData, dirtyX, dirtyY, dirtyWidth, dirtyHeight, false); } }); } public void markContentDirty() { visualsChanged(); } @Override protected boolean hasOverlappingContents() { return false; } }