I am writing 3d rendering module for an AWT/Swing application.
To provide good FPS, I can't draw using Swing/AWT methods and graphics. Instead, I obtain the Drawing Surface from the Canvas element, and then render directly to it. Something like this:
public class Window {
private Component canvas;
private JAWTDrawingSurface ds;
public static final JAWT awt;
static {
awt = JAWT.calloc();
awt.version(JAWT_VERSION_1_4);
if (!JAWT_GetAWT(awt))
throw new AssertionError("GetAWT failed");
}
public void lock() throws AWTException {
int lock = JAWT_DrawingSurface_Lock(ds, ds.Lock());
if ((lock & JAWT_LOCK_ERROR) != 0)
throw new AWTException("JAWT_DrawingSurface_Lock() failed");
}
public void unlock() throws AWTException {
JAWT_DrawingSurface_Unlock(ds, ds.Unlock());
}
public void Init2()
{
this.ds = JAWT_GetDrawingSurface(canvas, awt.GetDrawingSurface());
try
{
lock();
// Create GL Capabilities
unlock();
}
}
It works fine when I call it the first time.
But when I hide the canvas for any reason (for example minimizing window or displaying another panel instead of Canvas), the ds variable remains the same, but it doesn't work after that.
Basically, even if I make sure I call the variable only when it is visible and on top - any call using ds will throw an exception. For example lock() function stops working.
I'm wondering why's that?
Also I tried to basically obtain a new DS if I minimize and then maximize the window again, but this also doesn't work - the new DS address is returned as it should, but I can't use that new object just as I couldn't use the original one.
There's probably something stupid I'm missing here, but I can't figure out what.
Please help me sort this out. Thank you!
The solution:
When the Canvas is hidden, call eglMakeCurrent(eglDisplay,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT) to unbind the current context.
When you need to start drawing again, you need to do something like this:
public void Reinit()
{
System.err.println("Context Reinit()");
this.ds = JAWT_GetDrawingSurface(canvas, awt.GetDrawingSurface());
try
{
lock();
try
{
JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo());
JAWTX11DrawingSurfaceInfo dsiWin = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo());
this.display = dsiWin.display();
this.drawable = dsiWin.drawable();
eglDisplay = eglGetDisplay(display);
surface = eglCreateWindowSurface(eglDisplay,fbConfigs.get(0),drawable,(int[])null);
eglMakeCurrent(eglDisplay,surface,surface,context);
GLES.setCapabilities(glesCaps);
JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo());
}
finally
{
unlock();
System.err.printf("Unlock \n");
}
}
catch (Exception e)
{
System.err.println("JAWT Failed" + e.getMessage());
}
}
As You can see, I re-create the display and surface, but I use the previously created context for rendering, without needing to re-create it.
Related
There are a lot of questions and answer around concurrency, and mine could be similar to others, but for me it's not a duplicate as for some reason I must be missing something and hope to get some advice...
My question is more one where I need a second pair of eyes to point out what I'm doing incorrectly to enable my code to run in a background thread, but also updated the GUI, without freezing it.
Initially, a PDF file is uploaded to the application, using a task in a thread.
This works fine.
A progress bar is displayed, which animates without issue:
uploadFile()
public void uploadFile(File fileToProcess) {
fileBeingProcessed = fileToProcess;
Task<Parent> uploadingFileTask = new Task<Parent>() {
#Override
public Parent call() {
try {
progressBarStackPane.setVisible(true);
pdfPath = loadPDF(fileBeingProcessed.getAbsolutePath());
createPDFViewer();
openDocument();
} catch (IOException ex) {
java.util.logging.Logger.getLogger(MainSceneController.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
};
uploadingFileTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
fileHasBeenUploaded = true;
progressBarStackPane.setVisible(false);
uploadFilePane.setVisible(false);
tabPane.setVisible(true);
/* This is where I am getting issue, more so in createThumbnailPanels() */
setupThumbnailFlowPane();
createThumbnailPanels();
/****** ^^^^^^^^^^^^ ******/
}
});
uploadingFileTask.setOnFailed(evt -> {
uploadingFileTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(uploadingFileTask.getException().getSuppressed()));
});
Thread uploadingFileThread = new Thread(uploadingFileTask);
uploadingFileThread.start();
}
Once the document has been uploaded, it is displayed in a tab which allows the user to view the document.
There is a secondary tab, which, after upload, is disabled, until the completion of another task called createThumbnailPanelsTask;
However, before this task is ran, the FlowPane for the Thumbnail Panels is created. This seems to work without issue, and doesn't appear to be the cause of the GUI hanging (this is clearly a loop in createThumbnailPanelsTask, but for clarity I will show setupThumbnailFlowPane()):
setupThumbnailFlowPane()
public void setupThumbnailFlowPane() {
stage = model.getStage();
root = model.getRoot();
secondaryTabScrollPane.setFitToWidth(true);
secondaryTabScrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
/**
This will be removed from here when refactored but for now it is here,
I don't think this is anything to do with my issue
**/
Set<Node> nodes = secondaryTabScrollPane.lookupAll(".scroll-bar");
for (final Node node : nodes) {
if (node instanceof ScrollBar) {
ScrollBar sb = (ScrollBar) node;
if (sb.getOrientation() == Orientation.VERTICAL) {
sb.setUnitIncrement(30.0);
}
if (sb.getOrientation() == Orientation.HORIZONTAL) {
sb.setVisible(false);
}
}
}
secondaryTab = new FlowPane();
secondaryTab.setId("secondaryTab");
secondaryTab.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, new CornerRadii(0), new Insets(0))));
secondaryTab.prefWidthProperty().bind(stage.widthProperty());
secondaryTab.prefHeightProperty().bind(stage.heightProperty());
secondaryTab.setPrefWrapLength(stage.widthProperty().intValue() - 150);
secondaryTab.setHgap(5);
secondaryTab.setVgap(30);
secondaryTab.setBorder(new Border(new BorderStroke(Color.TRANSPARENT, BorderStrokeStyle.NONE, CornerRadii.EMPTY, new BorderWidths(8, 10, 20, 10))));
secondaryTab.setAlignment(Pos.CENTER);
}
Finally, createThumbnailPanels() is called, which is where I believe I am getting the problem.
What is suppose to happen is, after the document has uploaded, the upload file pane is hidden, revealing the Viewer Tab, and also the Secondary Tab.
The secondary tab is disabled at this point, and also has a loading image (a gif) on the left side of it.
The intended behaviour, is that the createThumbnailPanels() task will run in the background, and until it is complete, the tab will remain disabled, however, during this time, the gif image will be rotating, giving the impression there is some loading occurring.
Once the loading has completed, the gif is removed, and the tab is enabled, allowing the user to navigate to it, and see the generated thumbnail panels.
This all works, however, as mentioned, the task is hanging the GUI:
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex = 0; thumbIndex < numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
model.setThumbnailAt(tn, thumbIndex);
eventHandlers.setMouseEventsForThumbnails(tb);
/*
I have added this in as I am under the impression that a task runs in a background thread,
and then to update the GUI, I need to call this:
*/
Platform.runLater(() -> {
secondaryTab.getChildren().add(tb);
});
}
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
/*
Further GUI modification run in setOnSucceeded so it runs on main GUI thread(?)
*/
secondaryTabScrollPane.setContent(secondaryTab);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
Everything, bar the GUI hanging while it creates the panels, works fine.
Once they've been created, the GUI can be controlled again, the loading gif has been removed, the tab is enabled and the user can navigate to it and view the panels.
Clearly, there is something I am missing about concurrency here.
As mentioned, I was under the impression that a Task runs in a background thread, so I'm a little confused by why it doesn't appear to be doing this. Again, clearly something I am missing.
I have read, and read, and read about concurrency, but just can't seem to work out where in my approach I have gone wrong. I am tempted to try using a Service, however, I feel that I am just over complicating things by considering that, and that there is clearly a simply way to do what I want to achieve.
Any help will be greatly appreciated... a push in the right direction, or some clarification on where I have gone wrong in my understanding.
Thanks in advance, no doubt it's something obvious that once sorted will help me avoid this issue in future!
UPDATED CODE
createThumbnailPanels()
public void createThumbnailPanels() {
Task<Void> createThumbnailPanelsTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
if (model.getIcePdfDoc() != null) {
numberOfPagesInDocument = model.getIcePdfDoc().getNumberOfPages();
for (int thumbIndex= 0; thumbIndex< numberOfPagesInDocument; thumbIndex++) {
ThumbnailPanel tb = new ThumbnailPanel(thumbIndex, main, model);
Thumbnail tn = new Thumbnail(tb);
eventHandlers.setMouseEventsForThumbnails(tb);
model.setThumbnailAt(tn, thumbIndex);
model.setThumbnailPanels(tb);
}
setThumbnailPanelsToScrollPane();
}
return null;
}
};
createThumbnailPanelsTask.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
#Override
public void handle(WorkerStateEvent event) {
// setThumbnailPanelsToScrollPane();
}
});
createThumbnailPanelsTask.setOnFailed(evt -> {
createThumbnailPanelsTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(createThumbnailPanelsTask.getException().getSuppressed()));
});
Thread createThumbnailPanelsThread = new Thread(createThumbnailPanelsTask);
createThumbnailPanelsThread.start();
}
setThumbnailPanelsToScrollPane()
public void setThumbnailPanelsToScrollPane() {
Task<Void> setThumbnailPanelsToScrollPaneTask = new Task<Void>() {
//TODO: Need to check that it's a PDF
#Override
public Void call() {
Platform.runLater(() -> {
secondaryTab.getChildren().addAll(model.getThumbnailPanels());
secondaryTabScrollPane.setContent(main.informationExtractionPanel);
secondaryTab.setDisable(false);
secondaryTab.setGraphic(null);
});
return null;
}
};
setThumbnailPanelsToScrollPaneTask.setOnFailed(evt -> {
setThumbnailPanelsToScrollPaneTask.getException().printStackTrace(System.err);
System.err.println(Arrays.toString(setThumbnailPanelsToScrollPaneTask.getException().getSuppressed()));
});
Thread setThumbnailPanelsToScrollPaneThread = new Thread(setThumbnailPanelsToScrollPaneTask);
setThumbnailPanelsToScrollPaneThread.start();
}
FYI: If I call setThumbnailPanelsToScrollPane(); in the setOnSucceeded, it doesn't appear to work.
getChildren().add is running on the JavaFX GUI thread(thats what Platform.runLater does), but its only required to run it in a Platform.runLater if the parent that you add children to is connected to the root of the shown gui, that means you should be able to add children to a parent that is not connected to any root, and add the whole parent to the root at the end of the children addition process, if you're doing Platform.runLater in any asynchronous code it will run on the gui thread in your case it is in your asynchronous for loop adding ThumbnailPanels and if the number of them is large the gui will hang.
I'm fixing a JavaFX application on JDK 8. The app shows multiple images using something like "jLabel.setIcon(new Image(...))". I know their is a component to show images in JavaFX but it is not the issue (because I'm not allowed to modify neither that part from the source nor the FXML files).
My problem is when minimize or change the window ((using ALT+TAB)) and wait some time (even up 1 hour approx) to come back to the window, this doesn't show the images anymore. However, if I move the window, the images come back.
In an attempt to fix the problem (independently of use the minimize button or ALT+TAB), I added in the final of the init() method this piece of code:
public void init() {
...
stage.focusedProperty().addListener((observable, oldvalue, newvalue) -> {
if (newvalue) repaintImages();
});
}
private void repaintImages() {
Object obj = new Object();
synchronized (obj) {
jLabel.repaint();
jLabel.revalidate();
obj.notify();
}
try {
obj.wait();
} catch (Exception e) {
}
}
The code before runs repaintImages() after the windows is focused. I used synchronized with the idea to be ensured the code is executed in thread-safe (independently of lag), Anyway that doesn't fix the problem (images are missing yet). After that, I change this the code to use runLater():
public void init() {
...
stage.focusedProperty().addListener((observable, oldvalue, newvalue) -> {
if (newvalue) Platform.runLater(()->{repaintImages();});
});
}
private void repaintImages() {
Platform.runLater(() -> {
jLabel.repaint();
jLabel.revalidate();
});
}
And it doesn't work either. Please, any suggestion is welcome.
I know, I wrote a long question, but maybe a suggestion could be to move the window when it is focused. In that case, please give the correct way to do this because I tried and not worked too (using synchronized and runLater()). This is the piece of code I used to move the window (placing this into a synchronized block or in the lambda function in runLater()):
Translate elements = null;
if (positive_flag) {
elements = new Translate(1, 0, 0);
positive_flag = false;
} else {
elements = new Translate(-1, 0, 0);
positive_flag = true;
}
anchorPane.getTransforms().addAll(elements);
Finally, consider that to prove if any solution works, I need to wait even up 1h.
I'm currently developing a plugin which only renders an image to a map like this:
public class MapRendererTest extends MapRenderer {
private Image image;
public MapRendererTest(File file) {
try {
this.image = ImageIO.read(file);
}
catch (IOException e) {
e.printStackTrace();
}
}
#Override
public void render(MapView mapView, MapCanvas mapCanvas, Player player) {
mapCanvas.drawImage(0, 0, image);
}
}
everything works fine, but there is this little thing in the red circle that I want to remove but I don't know how.
Things I already tried
Setting an emtpy MapCursorCollection
MapView.setWorld to a world where no-one is playing.
Creating MapCursorCollection with a invisible cursor
It would be amazing if someone could help me with that.
Thanks in advance
To get rid of the cursor, you have to remove all the other map renderers or at least the CraftMapRanderer.
In order to do this, you have to do the following:
#EventHandler
public void onMapInitialized(MapInitializeEvent e) {
e.getMap().removeRenderer(e.getMap().getRenderers().get(0));
e.getMap().addRenderer(new MapRendererTest(new File(Main.getInstance().getDataFolder() + "/image.png")));
}
This only works of course if there is no other custom map renderer assigned to this map, so the only registered is the CraftMapRenderer.
If you have multiple MapRenderers you could loop through the list and remove them.
So I am using JavaFX and want to set up an experiment where I have to play many videos and interact with the user. For this setup I would like to write a function that receives a filename and plays the video with that name. But it should only return when the video is over, and not before. So something like this:
private void play(URI name){
MediaPlayer mediaPlayer = new MediaPlayer(new Media(name));
mediaPlayer.setAutoPlay(true);
view.setMediaPlayer(mediaPlayer);
}
However this function returns before the video is over, making the execution of the program a complete mess. I know MediaPlayer has a setOnEndOfMedia(Runnable) function but I am not sure how I could potentially use that to achieve my goal.
This is the code I have at the moment. But it does not work. The problem being that both videos are played at the same time, likely because the play function returns as soon as it has started the video. I have also tried just adding a while(true) loop at the end of the play function so that it gets stuck there until the return; in .setOnEndOfMedia is callled, that prevented any video's from being played at all.
CODE:
static MediaView view;
#Override
public void start(Stage stage) throws IOException, URISyntaxException, InterruptedException {
// Create full screen scene
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
stage.setFullScreen(true);
Scene scene = new Scene(new Group(), dim.getWidth(), dim.getHeight());
stage.setScene(scene);
stage.show();
// Create the view and add it to the Scene.
view = new MediaView();
((Group)scene.getRoot()).getChildren().add(view);
play("videol");
play("video2");
}
private static void play(String name) throws InterruptedException {
Media media = new Media(uri(name));
MediaPlayer player = new MediaPlayer(media);
player.setOnReady(() -> {
player.play();
player.setOnEndOfMedia(() -> {
player.dispose();
return;
});
});
view.setMediaPlayer(player);
}
private static String uri(String name) {
return Paths.get("clips/" + name + ".mp4").toUri().toString();
}
public static void main(String[] args) {
launch(args);
}
Keeping my original stuff since it is useful for others.
Anyways the problem with your code is that you are calling the method twice, right after itself. You need to call the method from within the EndOfMedia part. If you wish you can create a list and for the parameter when using recursion you can use list.poll() to take and remove the first string in the list. Then when the list is null you can just return to the interface. Just some ideas.
What I would suggest you use is to create a method for playing your music which takes a String as a parameter, this string would be then used to find its equivalent in a hashmap, say HashMap<String, MediaPlayer>. Then to the part where you actually need help. I suggest you write something like this:
mediaPlayer.setOnReady(() -> {
mediaPlayer.play();
mediaPlayer.setOnEndOfMedia(() -> {
mediaPlayer.dispose();
MusicPlayer();
});
});
Basically, you are playing the media when ready and then at the end disposing of it and calling the method again and in where my code uses recursion and calls the method MusicPlayer you can instead 'return'. Sorry if this is poorly explained, if you need further help just comment below.
EDIT: After fixing a few issues, the bigger issue that I am having is being caused by Apache POI which I am using. I am working on figuring that out now. Apparently it is being restricted by the Sandbox.
I'm very new to Swing, and created a small Swing app that I now need to have run via web start. I'm trying to use the FileOpenService and update a Text display. I think I am running into threading issues, because the FileOpenService dialog never appears, and my text display is not getting updated.
I can't really find any examples where they are doing anything different than I am right now.
Ideas?
Thanks!
Edit: I now have the FileOpenService dialog appearing. I changed my main to this:
public static void main(String[] args) throws Exception {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MainFrame();
}
});
}
However, I still can't get my display to update. This is where I am doing the update:
Runnable r = new Runnable() {
public void run() {
for (final String s : Logger.getMessages())
append(s + "\n");
}
};
try {
if (SwingUtilities.isEventDispatchThread())
r.run();
else
SwingUtilities.invokeAndWait(r);
}
and my append method:
private void append(Color c, String s) {// throws Exception {
StyleContext sc = StyleContext.getDefaultStyleContext();
AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY,
StyleConstants.Foreground, c);
int len = _textPaneLog.getDocument().getLength();
try {
_textPaneLog.getDocument().insertString(len, s, aset);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
You are calling *.openFileDialog(foo,bar) right?
FileOpenService fos = (FileOpenService)ServiceManager.lookup("javax.jnlp.FileOpenService");
FileContents fc = fos.openFileDialog(null, null);
Sorry if this is a dumb question, but until you update your question with source I can't think of much else.
The problem I was having had nothing to do with any of this. In another part of my code right near where I was trying to do this, I was causing an application exit. It was a hold over from something else I was trying out that got missed when I was working on implementing this.