Improve encapsulation and support of the rendering modes
This commit is contained in:
parent
61f3192760
commit
4a89e547a4
5 changed files with 133 additions and 76 deletions
|
@ -32,12 +32,14 @@ public final class Computer {
|
|||
private IEventListener listener;
|
||||
private final VirtualBoxManager manager;
|
||||
private MCFrameBuffer framebuffer;
|
||||
private final boolean direct;
|
||||
|
||||
public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox) {
|
||||
public Computer(PluginMain plugin, VirtualBoxManager manager, IVirtualBox vbox, boolean direct) {
|
||||
this.plugin = plugin;
|
||||
this.manager = manager;
|
||||
session = manager.getSessionObject();
|
||||
this.vbox = vbox;
|
||||
this.direct = direct;
|
||||
if (instance != null) throw new IllegalStateException("A computer already exists!");
|
||||
instance = this;
|
||||
}
|
||||
|
@ -107,7 +109,7 @@ public final class Computer {
|
|||
IProgress progress = console.powerUp(); // https://marc.info/?l=vbox-dev&m=142780789819967&w=2
|
||||
handler.setProgress(progress);
|
||||
handler.registerTo(progress.getEventSource()); //TODO: Show progress bar some way?
|
||||
val fb = new MCFrameBuffer(console.getDisplay());
|
||||
val fb = new MCFrameBuffer(console.getDisplay(), plugin, direct);
|
||||
if (plugin.runEmbedded.get())
|
||||
fb.start();
|
||||
String fbid = console.getDisplay().attachFramebuffer(0L,
|
||||
|
|
|
@ -14,13 +14,10 @@ import org.virtualbox_6_1.VirtualBoxManager;
|
|||
import sznp.virtualcomputer.events.VBoxEventHandler;
|
||||
import sznp.virtualcomputer.renderer.BukkitRenderer;
|
||||
import sznp.virtualcomputer.renderer.GPURenderer;
|
||||
import sznp.virtualcomputer.renderer.IRenderer;
|
||||
import sznp.virtualcomputer.util.Utils;
|
||||
import sznp.virtualcomputer.util.VBoxLib;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
|
@ -32,13 +29,6 @@ public class PluginMain extends ButtonPlugin {
|
|||
private VirtualBoxManager manager;
|
||||
|
||||
public static PluginMain Instance;
|
||||
/**
|
||||
* Only used if {@link #direct} is false.
|
||||
*/
|
||||
public static ByteBuffer allpixels; // It's set on each change
|
||||
private static final ArrayList<IRenderer> renderers = new ArrayList<>();
|
||||
public static boolean direct;
|
||||
public static boolean sendAll;
|
||||
@Getter
|
||||
private static boolean pluginEnabled; //The Bukkit plugin has to be enabled for the enable command to work
|
||||
|
||||
|
@ -66,6 +56,14 @@ public class PluginMain extends ButtonPlugin {
|
|||
* This can be useful to save resources as the plugin keeps the VirtualBox interface running while enabled.
|
||||
*/
|
||||
private final ConfigData<Boolean> autoEnable = getIConfig().getData("autoEnable", true);
|
||||
/**
|
||||
* The amount of rows to update if the slower, Bukkit renderer is being used.
|
||||
*/
|
||||
private final ConfigData<Integer> updateRows = getIConfig().getData("updateRows", 15, o -> (int) o, i -> BukkitRenderer.updatepixels = i);
|
||||
/**
|
||||
* Experimental. Whether all of the image data should be sent even on small changes. Defaults to true.
|
||||
*/
|
||||
private final ConfigData<Boolean> sendAll = getIConfig().getData("sendAll", true);
|
||||
|
||||
@Override
|
||||
public void pluginEnable() {
|
||||
|
@ -83,7 +81,6 @@ public class PluginMain extends ButtonPlugin {
|
|||
pluginEnabled = true;
|
||||
try {
|
||||
ConsoleCommandSender ccs = getServer().getConsoleSender();
|
||||
sendAll = getConfig().getBoolean("sendAll", true);
|
||||
ccs.sendMessage("§bInitializing VirtualBox...");
|
||||
String osname = System.getProperty("os.name").toLowerCase();
|
||||
final boolean windows;
|
||||
|
@ -102,7 +99,7 @@ public class PluginMain extends ButtonPlugin {
|
|||
if (notGoodDir.test(new File(vbpath)))
|
||||
vbpath = "/usr/lib/virtualbox";
|
||||
if (notGoodDir.test(new File(vbpath)))
|
||||
error("Could not find VirtualBox! Download from https://www.virtualbox.org/wiki/Downloads");
|
||||
error("Could not find VirtualBox! Download from https://www.virtualbox.org/wiki/Downloads", null);
|
||||
if (System.getProperty("vbox.home") == null || System.getProperty("vbox.home").isEmpty())
|
||||
System.setProperty("vbox.home", vbpath);
|
||||
if (System.getProperty("sun.boot.library.path") == null
|
||||
|
@ -118,49 +115,53 @@ public class PluginMain extends ButtonPlugin {
|
|||
}
|
||||
IVirtualBox vbox = manager.getVBox();
|
||||
(listener = new VBoxEventHandler()).registerTo(vbox.getEventSource());
|
||||
new Computer(this, manager, vbox); //Saves itself
|
||||
this.manager = manager;
|
||||
ccs.sendMessage("§bLoading Screen...");
|
||||
boolean direct;
|
||||
try {
|
||||
if (useGPU.get())
|
||||
if (useGPU.get()) {
|
||||
setupDirectRendering(ccs);
|
||||
else
|
||||
direct = true;
|
||||
} else {
|
||||
setupBukkitRendering(ccs);
|
||||
direct = false;
|
||||
}
|
||||
} catch (NoClassDefFoundError | Exception e) {
|
||||
e.printStackTrace();
|
||||
setupBukkitRendering(ccs);
|
||||
direct = false;
|
||||
}
|
||||
new Computer(this, manager, vbox, direct); //Saves itself
|
||||
ccs.sendMessage("§bLoaded!");
|
||||
val mlpl = new MouseLockerPlayerListener();
|
||||
mousetask = getServer().getScheduler().runTaskTimer(this, mlpl, 0, 0);
|
||||
getServer().getPluginManager().registerEvents(mlpl, this);
|
||||
|
||||
} catch (final Exception e) {
|
||||
getLogger().severe("A fatal error occured, disabling plugin!");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
throw new RuntimeException(e);
|
||||
error(null, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupDirectRendering(CommandSender ccs) throws Exception {
|
||||
for (short i = 0; i < MCX; i++)
|
||||
for (short j = 0; j < MCY; j++)
|
||||
renderers.add(new GPURenderer((short) (startID.get() + j * MCX + i), Bukkit.getWorlds().get(0), i, j));
|
||||
direct = true;
|
||||
new GPURenderer((short) (startID.get() + j * MCX + i), Bukkit.getWorlds().get(0), sendAll.get(), i, j);
|
||||
ccs.sendMessage("§bUsing Direct Renderer, all good");
|
||||
}
|
||||
|
||||
private void setupBukkitRendering(CommandSender ccs) {
|
||||
for (short i = 0; i < MCX * MCY; i++)
|
||||
renderers.add(new BukkitRenderer((short) (startID.get() + i), Bukkit.getWorlds().get(0), i * 128 * 128 * 4));
|
||||
direct = false;
|
||||
new BukkitRenderer((short) (startID.get() + i), Bukkit.getWorlds().get(0), i * 128 * 128 * 4, getLogger());
|
||||
ccs.sendMessage("§6Using Bukkit renderer");
|
||||
}
|
||||
|
||||
private void error(String message) {
|
||||
private void error(String message, Exception e) { //Message OR exception
|
||||
getLogger().severe("A fatal error occured, disabling plugin!");
|
||||
Bukkit.getPluginManager().disablePlugin(this);
|
||||
throw new RuntimeException(message);
|
||||
if (message != null)
|
||||
throw new RuntimeException(message);
|
||||
else
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -186,7 +187,6 @@ public class PluginMain extends ButtonPlugin {
|
|||
Computer.getInstance().pluginDisable(ccs);
|
||||
ccs.sendMessage("§aHuh.");
|
||||
saveConfig();
|
||||
renderers.clear();
|
||||
manager.cleanup();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,30 +10,33 @@ import java.awt.image.BufferedImage;
|
|||
import java.awt.image.DataBufferInt;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class BukkitRenderer extends MapRenderer implements IRenderer {
|
||||
private ByteBuffer allpixels;
|
||||
private BufferedImage image;
|
||||
private int startindex;
|
||||
private static ByteBuffer allpixels;
|
||||
private final BufferedImage image;
|
||||
private final int startindex;
|
||||
private final Logger logger;
|
||||
|
||||
/**
|
||||
* The raw pixel data from the machine in BGRA format
|
||||
* Updates the screen.
|
||||
*
|
||||
* @param allpixels The raw pixel data from the machine in BGRA format
|
||||
*/
|
||||
public void setAllPixels(ByteBuffer allpixels) {
|
||||
this.allpixels = allpixels;
|
||||
public static void update(ByteBuffer allpixels, int x, int y, int width, int height) {
|
||||
BukkitRenderer.allpixels = allpixels; //TODO: Only update what actually changes
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic implementation, should work on most versions
|
||||
*
|
||||
* @param id
|
||||
* The ID of the current map
|
||||
* @param world
|
||||
* The world to create new maps in
|
||||
* @param startindex
|
||||
* The index to start from in allpixels
|
||||
*
|
||||
* @param id The ID of the current map
|
||||
* @param world The world to create new maps in
|
||||
* @param startindex The index
|
||||
* @param logger The plugin's logger
|
||||
*/
|
||||
public BukkitRenderer(short id, World world, int startindex) {
|
||||
public BukkitRenderer(short id, World world, int startindex, Logger logger) {
|
||||
this.logger = logger;
|
||||
MapView map = IRenderer.prepare(id, world);
|
||||
map.addRenderer(this);
|
||||
this.startindex = startindex;
|
||||
|
@ -72,12 +75,12 @@ public class BukkitRenderer extends MapRenderer implements IRenderer {
|
|||
|
||||
long diff = System.nanoTime() - time;
|
||||
if (TimeUnit.NANOSECONDS.toMillis(diff) > 40) {
|
||||
System.out.println("Map rendering took " + TimeUnit.NANOSECONDS.toMillis(diff) + " ms");
|
||||
logger.warning("Map rendering took " + TimeUnit.NANOSECONDS.toMillis(diff) + " ms");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Progess: " + progress);
|
||||
System.out.println("UpdatePixels: " + updatepixels);
|
||||
logger.warning("Progess: " + progress);
|
||||
logger.warning("UpdatePixels: " + updatepixels);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.function.BiConsumer;
|
|||
public class GPURenderer extends MapRenderer implements IRenderer {
|
||||
private byte[] buffer;
|
||||
private final GPURendererInternal kernel;
|
||||
private final boolean sendAll;
|
||||
private int mapx, mapy;
|
||||
//Store at central location after conversion
|
||||
private static int[] colors_;
|
||||
|
@ -32,7 +33,8 @@ public class GPURenderer extends MapRenderer implements IRenderer {
|
|||
private static boolean enabled = true;
|
||||
private static boolean warned = false;
|
||||
|
||||
public GPURenderer(short id, World world, int mapx, int mapy) throws Exception {
|
||||
public GPURenderer(short id, World world, boolean sendAll, int mapx, int mapy) throws Exception {
|
||||
this.sendAll = sendAll;
|
||||
MapView map = IRenderer.prepare(id, world);
|
||||
if (map == null) {
|
||||
kernel = null;
|
||||
|
@ -90,7 +92,7 @@ public class GPURenderer extends MapRenderer implements IRenderer {
|
|||
PluginMain.Instance.getLogger().warning("Server performance may be affected"); //TODO: Index 0 out of range 0
|
||||
}
|
||||
}
|
||||
if (!PluginMain.sendAll) {
|
||||
if (!sendAll) {
|
||||
synchronized (kernel) {
|
||||
if (changedX >= (mapx + 1) * 128 || changedY >= (mapy + 1) * 128
|
||||
|| changedX + changedWidth < mapx * 128 || changedY + changedHeight < mapy * 128) {
|
||||
|
@ -134,7 +136,7 @@ public class GPURenderer extends MapRenderer implements IRenderer {
|
|||
public static void update(byte[] pixels, int width, int height, int changedX, int changedY, int changedWidth, int changedHeight) {
|
||||
for (GPURenderer r : renderers) {
|
||||
synchronized (r.kernel) {
|
||||
if (!PluginMain.sendAll) {
|
||||
if (!r.sendAll) {
|
||||
if (changedX < r.changedX)
|
||||
r.changedX = changedX;
|
||||
if (changedY < r.changedY)
|
||||
|
|
|
@ -2,8 +2,8 @@ package sznp.virtualcomputer.renderer;
|
|||
|
||||
import com.sun.jna.Pointer;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.val;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.virtualbox_6_1.Holder;
|
||||
|
@ -15,12 +15,18 @@ import sznp.virtualcomputer.util.COMUtils;
|
|||
import sznp.virtualcomputer.util.IMCFrameBuffer;
|
||||
import sznp.virtualcomputer.util.Timing;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MCFrameBuffer implements IMCFrameBuffer {
|
||||
private final IDisplay display;
|
||||
private final Holder<IDisplaySourceBitmap> holder = new Holder<>();
|
||||
private final Logger logger;
|
||||
private final PluginMain plugin;
|
||||
private final boolean embedded;
|
||||
private final boolean direct;
|
||||
private BukkitTask tt;
|
||||
/**
|
||||
* Used when running embedded
|
||||
|
@ -29,45 +35,59 @@ public class MCFrameBuffer implements IMCFrameBuffer {
|
|||
/**
|
||||
* Used when not running embedded
|
||||
*/
|
||||
private byte[] screenImage; //TODO: Remove PluginMain.allpixels (and other PluginMain references)
|
||||
private byte[] screenImage;
|
||||
/**
|
||||
* Used when running in indirect mode, not embedded
|
||||
*/
|
||||
private ByteBuffer screenBuffer;
|
||||
private int width;
|
||||
private int height;
|
||||
@Getter
|
||||
@Setter
|
||||
private String id;
|
||||
private final AtomicBoolean shouldUpdate = new AtomicBoolean();
|
||||
private final AtomicIntegerArray updateParameters = new AtomicIntegerArray(4);
|
||||
private boolean running;
|
||||
|
||||
/**
|
||||
* Creates a new framebuffer that receives images from the VM and sends the image data to Minecraft.
|
||||
*
|
||||
* @param display The VM display to use - TODO: Multiple monitors
|
||||
* @param plugin The plugin
|
||||
* @param direct Whether the GPU rendering is used
|
||||
*/
|
||||
public MCFrameBuffer(IDisplay display, PluginMain plugin, boolean direct) {
|
||||
this.display = display;
|
||||
this.plugin = plugin;
|
||||
this.logger = plugin.getLogger();
|
||||
this.embedded = plugin.runEmbedded.get(); //Don't change even if the config got updated while running
|
||||
this.direct = direct;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyChange(long screenId, long xOrigin, long yOrigin, long width, long height) {
|
||||
if (tt != null)
|
||||
tt.cancel();
|
||||
tt = Bukkit.getScheduler().runTaskAsynchronously(PluginMain.Instance, () -> {
|
||||
tt = Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
synchronized (this) { //If a change occurs twice, then wait for it
|
||||
try {
|
||||
if (!PluginMain.Instance.runEmbedded.get()) { //Running separately
|
||||
if (!embedded) { //Running separately
|
||||
this.width = (int) width;
|
||||
this.height = (int) height;
|
||||
if (screenImage == null || screenImage.length != width * height * 4)
|
||||
if (screenImage == null || screenImage.length != width * height * 4) {
|
||||
screenImage = new byte[(int) (width * height * 4)];
|
||||
updateScreen(screenImage);
|
||||
screenBuffer = ByteBuffer.wrap(screenImage);
|
||||
}
|
||||
updateScreen((int) xOrigin, (int) yOrigin, (int) width, (int) height);
|
||||
return;
|
||||
}
|
||||
display.querySourceBitmap(0L, holder);
|
||||
long[] ptr = new long[1], w = new long[1], h = new long[1], bpp = new long[1], bpl = new long[1], pf = new long[1];
|
||||
COMUtils.queryBitmapInfo(holder.value, ptr, w, h, bpp, bpl, pf);
|
||||
if (PluginMain.direct) {
|
||||
pointer = new Pointer(ptr[0]);
|
||||
this.width = (int) w[0];
|
||||
this.height = (int) h[0];
|
||||
GPURenderer.update(pointer.getByteArray(0L, (int) (w[0] * h[0] * 4)), (int) w[0], (int) h[0], 0, 0, this.width, this.height);
|
||||
} else {
|
||||
PluginMain.allpixels = new Pointer(ptr[0]).getByteBuffer(0L, width * height * 4);
|
||||
if (width * height > 640 * 480)
|
||||
PluginMain.allpixels.limit(640 * 480 * 4);
|
||||
else
|
||||
PluginMain.allpixels.limit((int) (width * height * 4));
|
||||
}
|
||||
pointer = new Pointer(ptr[0]);
|
||||
this.width = (int) w[0];
|
||||
this.height = (int) h[0];
|
||||
updateScreen(0, 0, (int) width, (int) height);
|
||||
} catch (VBoxException e) {
|
||||
if (e.getResultCode() == 0x80070005)
|
||||
return; // Machine is being powered down
|
||||
|
@ -85,10 +105,14 @@ public class MCFrameBuffer implements IMCFrameBuffer {
|
|||
public void notifyUpdate(long x, long y, long width, long height) {
|
||||
/*if (this.width > 1024 || this.height > 768)
|
||||
return;*/
|
||||
if (!PluginMain.direct || shouldUpdate.get())
|
||||
if (!direct || shouldUpdate.get())
|
||||
return; //Don't wait for lock, ignore update since we're updating everything anyway - TODO: Not always
|
||||
synchronized (this) {
|
||||
shouldUpdate.set(true);
|
||||
updateParameters.set(0, (int) x);
|
||||
updateParameters.set(1, (int) y);
|
||||
updateParameters.set(2, (int) width);
|
||||
updateParameters.set(3, (int) height);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
@ -97,19 +121,19 @@ public class MCFrameBuffer implements IMCFrameBuffer {
|
|||
public void notifyUpdateImage(long x, long y, long width, long height, byte[] image) {
|
||||
System.out.println("Update image!");
|
||||
if (this.width == 0 || this.height == 0) {
|
||||
PluginMain.Instance.getLogger().warning("Received screen image before resolution change!");
|
||||
logger.warning("Received screen image before resolution change!");
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < height; i++) //Copy lines of the screen in a fast way
|
||||
System.arraycopy(image, (int) (i * width * 4), screenImage, (int) (x + y * this.width * 4), (int) width * 4);
|
||||
updateScreen(image);
|
||||
updateScreen((int) x, (int) y, (int) width, (int) height);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (!PluginMain.direct)
|
||||
if (!direct)
|
||||
return;
|
||||
running = true;
|
||||
Bukkit.getScheduler().runTaskAsynchronously(PluginMain.Instance, () -> {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
try {
|
||||
while (running) {
|
||||
synchronized (this) {
|
||||
|
@ -121,7 +145,7 @@ public class MCFrameBuffer implements IMCFrameBuffer {
|
|||
continue;
|
||||
}
|
||||
if (!running) return;
|
||||
updateScreen(pointer.getByteArray(0L, this.width * this.height * 4));
|
||||
updateScreen(updateParameters.get(0), updateParameters.get(1), updateParameters.get(2), updateParameters.get(3));
|
||||
shouldUpdate.set(false);
|
||||
}
|
||||
}
|
||||
|
@ -130,11 +154,37 @@ public class MCFrameBuffer implements IMCFrameBuffer {
|
|||
});
|
||||
}
|
||||
|
||||
private void updateScreen(byte[] pixels) {
|
||||
Timing t = new Timing(); //TODO: Add support for only sending changed fragments
|
||||
GPURenderer.update(pixels, this.width, this.height, (int) 0, (int) 0, (int) width, (int) height);
|
||||
private void updateScreenDirectInternal(byte[] pixels, int x, int y, int width, int height) {
|
||||
Timing t = new Timing();
|
||||
GPURenderer.update(pixels, this.width, this.height, x, y, width, height);
|
||||
if (t.elapsedMS() > 60) //Typically 1ms max
|
||||
PluginMain.Instance.getLogger().warning("Update took " + t.elapsedMS() + "ms");
|
||||
logger.warning("Update took " + t.elapsedMS() + "ms");
|
||||
}
|
||||
|
||||
private void updateScreenIndirectInternal(ByteBuffer buffer, int x, int y, int width, int height) {
|
||||
if (this.width * this.height > 640 * 480)
|
||||
buffer.limit(640 * 480 * 4);
|
||||
else
|
||||
buffer.limit(this.width * this.height * 4);
|
||||
BukkitRenderer.update(buffer, x, y, width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the screen when the VM is embedded or when it isn't.
|
||||
*
|
||||
* @param x The x of change - passed along to the renderer to use
|
||||
* @param y The y of change - passed along to the renderer to use
|
||||
* @param width The width of change - passed along to the renderer to use
|
||||
* @param height The height of change - passed along to the renderer to use
|
||||
*/
|
||||
private void updateScreen(int x, int y, int width, int height) {
|
||||
if (direct) {
|
||||
val arr = embedded ? pointer.getByteArray(0L, this.width * this.height * 4) : screenImage;
|
||||
updateScreenDirectInternal(arr, x, y, width, height);
|
||||
} else {
|
||||
val bb = embedded ? pointer.getByteBuffer(0L, (long) this.width * this.height * 4) : screenBuffer;
|
||||
updateScreenIndirectInternal(bb, x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
|
|
Loading…
Reference in a new issue