blob: c5f30976958019cb7e27b7eff5b8578eaec0a96a [file] [log] [blame]
/*
* Copyright (c) 2005, 2008, Oracle and/or its affiliates. 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.pipe;
import java.awt.BasicStroke;
import java.awt.Polygon;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.IllegalPathStateException;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import sun.java2d.SunGraphics2D;
import sun.java2d.loops.ProcessPath;
import static sun.java2d.pipe.BufferedOpCodes.*;
/**
* Base class for enqueuing rendering operations in a single-threaded
* rendering environment. Instead of each operation being rendered
* immediately by the underlying graphics library, the operation will be
* added to the provided RenderQueue, which will be processed at a later
* time by a single thread.
*
* This class provides implementations of drawLine(), drawRect(), drawPoly(),
* fillRect(), draw(Shape), and fill(Shape), which are useful for a
* hardware-accelerated renderer. The other draw*() and fill*() methods
* simply delegate to draw(Shape) and fill(Shape), respectively.
*/
public abstract class BufferedRenderPipe
implements PixelDrawPipe, PixelFillPipe, ShapeDrawPipe, ParallelogramPipe
{
ParallelogramPipe aapgrampipe = new AAParallelogramPipe();
static final int BYTES_PER_POLY_POINT = 8;
static final int BYTES_PER_SCANLINE = 12;
static final int BYTES_PER_SPAN = 16;
protected RenderQueue rq;
protected RenderBuffer buf;
private BufferedDrawHandler drawHandler;
public BufferedRenderPipe(RenderQueue rq) {
this.rq = rq;
this.buf = rq.getBuffer();
this.drawHandler = new BufferedDrawHandler();
}
public ParallelogramPipe getAAParallelogramPipe() {
return aapgrampipe;
}
/**
* Validates the state in the provided SunGraphics2D object and sets up
* any special resources for this operation (e.g. enabling gradient
* shading).
*/
protected abstract void validateContext(SunGraphics2D sg2d);
protected abstract void validateContextAA(SunGraphics2D sg2d);
public void drawLine(SunGraphics2D sg2d,
int x1, int y1, int x2, int y2)
{
int transx = sg2d.transX;
int transy = sg2d.transY;
rq.lock();
try {
validateContext(sg2d);
rq.ensureCapacity(20);
buf.putInt(DRAW_LINE);
buf.putInt(x1 + transx);
buf.putInt(y1 + transy);
buf.putInt(x2 + transx);
buf.putInt(y2 + transy);
} finally {
rq.unlock();
}
}
public void drawRect(SunGraphics2D sg2d,
int x, int y, int width, int height)
{
rq.lock();
try {
validateContext(sg2d);
rq.ensureCapacity(20);
buf.putInt(DRAW_RECT);
buf.putInt(x + sg2d.transX);
buf.putInt(y + sg2d.transY);
buf.putInt(width);
buf.putInt(height);
} finally {
rq.unlock();
}
}
public void fillRect(SunGraphics2D sg2d,
int x, int y, int width, int height)
{
rq.lock();
try {
validateContext(sg2d);
rq.ensureCapacity(20);
buf.putInt(FILL_RECT);
buf.putInt(x + sg2d.transX);
buf.putInt(y + sg2d.transY);
buf.putInt(width);
buf.putInt(height);
} finally {
rq.unlock();
}
}
public void drawRoundRect(SunGraphics2D sg2d,
int x, int y, int width, int height,
int arcWidth, int arcHeight)
{
draw(sg2d, new RoundRectangle2D.Float(x, y, width, height,
arcWidth, arcHeight));
}
public void fillRoundRect(SunGraphics2D sg2d,
int x, int y, int width, int height,
int arcWidth, int arcHeight)
{
fill(sg2d, new RoundRectangle2D.Float(x, y, width, height,
arcWidth, arcHeight));
}
public void drawOval(SunGraphics2D sg2d,
int x, int y, int width, int height)
{
draw(sg2d, new Ellipse2D.Float(x, y, width, height));
}
public void fillOval(SunGraphics2D sg2d,
int x, int y, int width, int height)
{
fill(sg2d, new Ellipse2D.Float(x, y, width, height));
}
public void drawArc(SunGraphics2D sg2d,
int x, int y, int width, int height,
int startAngle, int arcAngle)
{
draw(sg2d, new Arc2D.Float(x, y, width, height,
startAngle, arcAngle,
Arc2D.OPEN));
}
public void fillArc(SunGraphics2D sg2d,
int x, int y, int width, int height,
int startAngle, int arcAngle)
{
fill(sg2d, new Arc2D.Float(x, y, width, height,
startAngle, arcAngle,
Arc2D.PIE));
}
protected void drawPoly(final SunGraphics2D sg2d,
final int[] xPoints, final int[] yPoints,
final int nPoints, final boolean isClosed)
{
if (xPoints == null || yPoints == null) {
throw new NullPointerException("coordinate array");
}
if (xPoints.length < nPoints || yPoints.length < nPoints) {
throw new ArrayIndexOutOfBoundsException("coordinate array");
}
if (nPoints < 2) {
// render nothing
return;
} else if (nPoints == 2 && !isClosed) {
// render a simple line
drawLine(sg2d, xPoints[0], yPoints[0], xPoints[1], yPoints[1]);
return;
}
rq.lock();
try {
validateContext(sg2d);
int pointBytesRequired = nPoints * BYTES_PER_POLY_POINT;
int totalBytesRequired = 20 + pointBytesRequired;
if (totalBytesRequired <= buf.capacity()) {
if (totalBytesRequired > buf.remaining()) {
// process the queue first and then enqueue the points
rq.flushNow();
}
buf.putInt(DRAW_POLY);
// enqueue parameters
buf.putInt(nPoints);
buf.putInt(isClosed ? 1 : 0);
buf.putInt(sg2d.transX);
buf.putInt(sg2d.transY);
// enqueue the points
buf.put(xPoints, 0, nPoints);
buf.put(yPoints, 0, nPoints);
} else {
// queue is too small to accomodate all points; perform the
// operation directly on the queue flushing thread
rq.flushAndInvokeNow(new Runnable() {
public void run() {
drawPoly(xPoints, yPoints,
nPoints, isClosed,
sg2d.transX, sg2d.transY);
}
});
}
} finally {
rq.unlock();
}
}
protected abstract void drawPoly(int[] xPoints, int[] yPoints,
int nPoints, boolean isClosed,
int transX, int transY);
public void drawPolyline(SunGraphics2D sg2d,
int[] xPoints, int[] yPoints,
int nPoints)
{
drawPoly(sg2d, xPoints, yPoints, nPoints, false);
}
public void drawPolygon(SunGraphics2D sg2d,
int[] xPoints, int[] yPoints,
int nPoints)
{
drawPoly(sg2d, xPoints, yPoints, nPoints, true);
}
public void fillPolygon(SunGraphics2D sg2d,
int[] xPoints, int[] yPoints,
int nPoints)
{
fill(sg2d, new Polygon(xPoints, yPoints, nPoints));
}
private class BufferedDrawHandler
extends ProcessPath.DrawHandler
{
BufferedDrawHandler() {
// these are bogus values; the caller will use validate()
// to ensure that they are set properly prior to each usage
super(0, 0, 0, 0);
}
/**
* This method needs to be called prior to each draw/fillPath()
* operation to ensure the clip bounds are up to date.
*/
void validate(SunGraphics2D sg2d) {
Region clip = sg2d.getCompClip();
setBounds(clip.getLoX(), clip.getLoY(),
clip.getHiX(), clip.getHiY(),
sg2d.strokeHint);
}
/**
* drawPath() support...
*/
public void drawLine(int x1, int y1, int x2, int y2) {
// assert rq.lock.isHeldByCurrentThread();
rq.ensureCapacity(20);
buf.putInt(DRAW_LINE);
buf.putInt(x1);
buf.putInt(y1);
buf.putInt(x2);
buf.putInt(y2);
}
public void drawPixel(int x, int y) {
// assert rq.lock.isHeldByCurrentThread();
rq.ensureCapacity(12);
buf.putInt(DRAW_PIXEL);
buf.putInt(x);
buf.putInt(y);
}
/**
* fillPath() support...
*/
private int scanlineCount;
private int scanlineCountIndex;
private int remainingScanlines;
private void resetFillPath() {
buf.putInt(DRAW_SCANLINES);
scanlineCountIndex = buf.position();
buf.putInt(0);
scanlineCount = 0;
remainingScanlines = buf.remaining() / BYTES_PER_SCANLINE;
}
private void updateScanlineCount() {
buf.putInt(scanlineCountIndex, scanlineCount);
}
/**
* Called from fillPath() to indicate that we are about to
* start issuing drawScanline() calls.
*/
public void startFillPath() {
rq.ensureCapacity(20); // to ensure room for at least a scanline
resetFillPath();
}
public void drawScanline(int x1, int x2, int y) {
if (remainingScanlines == 0) {
updateScanlineCount();
rq.flushNow();
resetFillPath();
}
buf.putInt(x1);
buf.putInt(x2);
buf.putInt(y);
scanlineCount++;
remainingScanlines--;
}
/**
* Called from fillPath() to indicate that we are done
* issuing drawScanline() calls.
*/
public void endFillPath() {
updateScanlineCount();
}
}
protected void drawPath(SunGraphics2D sg2d,
Path2D.Float p2df, int transx, int transy)
{
rq.lock();
try {
validateContext(sg2d);
drawHandler.validate(sg2d);
ProcessPath.drawPath(drawHandler, p2df, transx, transy);
} finally {
rq.unlock();
}
}
protected void fillPath(SunGraphics2D sg2d,
Path2D.Float p2df, int transx, int transy)
{
rq.lock();
try {
validateContext(sg2d);
drawHandler.validate(sg2d);
drawHandler.startFillPath();
ProcessPath.fillPath(drawHandler, p2df, transx, transy);
drawHandler.endFillPath();
} finally {
rq.unlock();
}
}
private native int fillSpans(RenderQueue rq, long buf,
int pos, int limit,
SpanIterator si, long iterator,
int transx, int transy);
protected void fillSpans(SunGraphics2D sg2d, SpanIterator si,
int transx, int transy)
{
rq.lock();
try {
validateContext(sg2d);
rq.ensureCapacity(24); // so that we have room for at least a span
int newpos = fillSpans(rq, buf.getAddress(),
buf.position(), buf.capacity(),
si, si.getNativeIterator(),
transx, transy);
buf.position(newpos);
} finally {
rq.unlock();
}
}
public void fillParallelogram(SunGraphics2D sg2d,
double ux1, double uy1,
double ux2, double uy2,
double x, double y,
double dx1, double dy1,
double dx2, double dy2)
{
rq.lock();
try {
validateContext(sg2d);
rq.ensureCapacity(28);
buf.putInt(FILL_PARALLELOGRAM);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) dx1);
buf.putFloat((float) dy1);
buf.putFloat((float) dx2);
buf.putFloat((float) dy2);
} finally {
rq.unlock();
}
}
public void drawParallelogram(SunGraphics2D sg2d,
double ux1, double uy1,
double ux2, double uy2,
double x, double y,
double dx1, double dy1,
double dx2, double dy2,
double lw1, double lw2)
{
rq.lock();
try {
validateContext(sg2d);
rq.ensureCapacity(36);
buf.putInt(DRAW_PARALLELOGRAM);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) dx1);
buf.putFloat((float) dy1);
buf.putFloat((float) dx2);
buf.putFloat((float) dy2);
buf.putFloat((float) lw1);
buf.putFloat((float) lw2);
} finally {
rq.unlock();
}
}
private class AAParallelogramPipe implements ParallelogramPipe {
public void fillParallelogram(SunGraphics2D sg2d,
double ux1, double uy1,
double ux2, double uy2,
double x, double y,
double dx1, double dy1,
double dx2, double dy2)
{
rq.lock();
try {
validateContextAA(sg2d);
rq.ensureCapacity(28);
buf.putInt(FILL_AAPARALLELOGRAM);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) dx1);
buf.putFloat((float) dy1);
buf.putFloat((float) dx2);
buf.putFloat((float) dy2);
} finally {
rq.unlock();
}
}
public void drawParallelogram(SunGraphics2D sg2d,
double ux1, double uy1,
double ux2, double uy2,
double x, double y,
double dx1, double dy1,
double dx2, double dy2,
double lw1, double lw2)
{
rq.lock();
try {
validateContextAA(sg2d);
rq.ensureCapacity(36);
buf.putInt(DRAW_AAPARALLELOGRAM);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) dx1);
buf.putFloat((float) dy1);
buf.putFloat((float) dx2);
buf.putFloat((float) dy2);
buf.putFloat((float) lw1);
buf.putFloat((float) lw2);
} finally {
rq.unlock();
}
}
}
public void draw(SunGraphics2D sg2d, Shape s) {
if (sg2d.strokeState == sg2d.STROKE_THIN) {
if (s instanceof Polygon) {
if (sg2d.transformState < sg2d.TRANSFORM_TRANSLATESCALE) {
Polygon p = (Polygon)s;
drawPolygon(sg2d, p.xpoints, p.ypoints, p.npoints);
return;
}
}
Path2D.Float p2df;
int transx, transy;
if (sg2d.transformState <= sg2d.TRANSFORM_INT_TRANSLATE) {
if (s instanceof Path2D.Float) {
p2df = (Path2D.Float)s;
} else {
p2df = new Path2D.Float(s);
}
transx = sg2d.transX;
transy = sg2d.transY;
} else {
p2df = new Path2D.Float(s, sg2d.transform);
transx = 0;
transy = 0;
}
drawPath(sg2d, p2df, transx, transy);
} else if (sg2d.strokeState < sg2d.STROKE_CUSTOM) {
ShapeSpanIterator si = LoopPipe.getStrokeSpans(sg2d, s);
try {
fillSpans(sg2d, si, 0, 0);
} finally {
si.dispose();
}
} else {
fill(sg2d, sg2d.stroke.createStrokedShape(s));
}
}
public void fill(SunGraphics2D sg2d, Shape s) {
int transx, transy;
if (sg2d.strokeState == sg2d.STROKE_THIN) {
// Here we are able to use fillPath() for
// high-quality fills.
Path2D.Float p2df;
if (sg2d.transformState <= sg2d.TRANSFORM_INT_TRANSLATE) {
if (s instanceof Path2D.Float) {
p2df = (Path2D.Float)s;
} else {
p2df = new Path2D.Float(s);
}
transx = sg2d.transX;
transy = sg2d.transY;
} else {
p2df = new Path2D.Float(s, sg2d.transform);
transx = 0;
transy = 0;
}
fillPath(sg2d, p2df, transx, transy);
return;
}
AffineTransform at;
if (sg2d.transformState <= sg2d.TRANSFORM_INT_TRANSLATE) {
// Transform (translation) will be done by FillSpans (we could
// delegate to fillPolygon() here, but most hardware accelerated
// libraries cannot handle non-convex polygons, so we will use
// the FillSpans approach by default)
at = null;
transx = sg2d.transX;
transy = sg2d.transY;
} else {
// Transform will be done by the PathIterator
at = sg2d.transform;
transx = transy = 0;
}
ShapeSpanIterator ssi = LoopPipe.getFillSSI(sg2d);
try {
// Subtract transx/y from the SSI clip to match the
// (potentially untranslated) geometry fed to it
Region clip = sg2d.getCompClip();
ssi.setOutputAreaXYXY(clip.getLoX() - transx,
clip.getLoY() - transy,
clip.getHiX() - transx,
clip.getHiY() - transy);
ssi.appendPath(s.getPathIterator(at));
fillSpans(sg2d, ssi, transx, transy);
} finally {
ssi.dispose();
}
}
}