Wednesday, August 16, 2017

resizing-a-t-viewport-3d

I have almost finished an application or two. The two applications are using the same technology. Both are Delphi FMX applications which use a TViewport3D as the main component which fills the whole client area of the window, at least most of the time. There have been problems with resizing the component. Note that I am writing a multiplatform HD application, not the 3D application. There is a little difference between the two. I have always been using the HD application where the TViewport3D is a normal component on the form.

This blog post is a summary. I am writing this without looking at the source, no IDE open, and I will not show code. (I have reset the template of this blog to the default without any support for syntax highlighting because the browser complained about using http link to the resources and I do not want that.)

Back to the TViewport3D, and from here on I will just say Viewport. The first problem is obvious when you look at the Resize method. The Viewport will destroy the current context and create a new one whenever the component is resized.

First thing I did was to ensure that the context is not destroyed but kept alive when resizing. This has revealed a bug. I need to set one more buffer resource to nil. It is in method DoResize of TDX11Context. This is where other resources are also set to nil, for example the render target. You want to add FCopyBuffer to the list (RSP-18850).

Then I was looking at the number of times resize is called during a manual resize of the application window with the mouse. I needed to handle a windows message not handled so far and act only when resize has finished (WM_EXITSIZEMOVE, RSP-18851). You will always have a hard time debugging or finally improving something related to resize if you do not have that, I think.

It is important that the Viewport is not aligned to client and automatically resized. Just set the size of it whenever something has finished changing. When I say size, I mean Size. Do not set Width followed by Height, you want to use an instance of TControlSize. I will get back to alignment in a moment.

For some time, focus on application startup. How many times is the Viewport resized when the application starts up. It should be one.

Turns out that Width and Height are set to a default size in the constructor which triggers a resize. I got rid of that (RSP-17296). It felt much better, at that time my application did set the size of the Viewport to an area greater than zero, it was aligned to client area of the parent.

I found out that the second application behaved better than the first and why. In the process, I switched alignment to none which resulted in an interesting situation. Width and Height of the Viewport were initially zero. This means that the context is nil and will stay nil, until you set the size. In my code, there was one location where I needed to test for context nil. I did that and reached the next stage.

It was in the Paint method. If the context is nil, the bitmap is also nil. Paint should test for a nil bitmap and exit immediately without an attempt to draw anything (RSP-18852). With that in place I could start up the application with an empty Viewport. This makes a good test. You should be able to run the application with a Viewport area of zero without a crash. If the test passes you can go on and give it a decent size. Then it will create a context and draw as intended.

As I have said above, it should do this just once when the application starts up. Use a draw counter to check. If it instead will draw a few times during startup this will probably be OK. And if resizing seems to work without problems then this is good for you, but keep in mind there might be a problem.

Back to alignment. Do not align. Then size is not adjusted during a resize. Do this when resize has ended. (Nice that I have the exit resize event.) You will have more confidence that the application will survive wild excessive resizing by the end user, who does not seem to feel the pain.

My application uses a modified context class. I will create and use a special buffer resource on the GPU. There is a show stopper problem when the context is recreated too often. You may not see this in a normal application. But there is a potential problem with resizing a TViewport3D in 10.2.1 and before.

Monday, August 14, 2017

new-sample-data-repo

OK, great news, a new version of the application is available from the Windows 10 Store. And I also have created a sample data repository on GitHub. With the initial commit, I posted some text data files. I know that I will have to fine tune and update these files in the future. At least they are at GitHub now where they belong. The reason why I think they may need an update is that I have many branches of the application with a different set of features. And I had to slightly change samples to work with the feature reduced application.

There is a feature I like very much, you can embed or imprint the model text data into a screenshot of the rendered image. I will just modify the red channel of the pixels in the png image, starting at the top row. Then you can drop the image on the drop target of the application, and the application will load the model data. What you get is the reproduced image, fully alive within the application.

Today I wanted to create new images with imprinted data and post them to the sample-data repository. I used the text data files from the repo, dropped them on the application. Then I resized the application to a 512 pixels square format and executed the CopyImprintedBitmap action, which will put an image into the clipboard. Then I pasted into Microsoft Paint and saved as png with the same name as the text data file. In a way, I ate my own dogfood.

And, you may have guessed it, I am now ready to write about the problems.

In the full application, I have implemented actions that will resize the window to a specific size. Many of the icon sizes and photo formats and mobile device form factor sizes are available, including the 512 pixels square. In the published app, I have removed that feature. I should perhaps say that I have chosen a version for publication which is clean and lean and sandboxed. Yes, the end user running the published app can produce the image with imprinted data. It will have the size of the application window when the action was executed. When the image will be resized in another program, the imprinted data will be lost though. You have to set the size of the application window before you create the image. That is why I used the full featured version to do that. I made a note and will probably add the feature to the published app.

Dropping the imprinted image worked great, for the first two images. The third one did not show as expected. But I found out why. Not sure if I should call it a bug or an issue. I think I will update the published app, so that there is no difference between the full internal and sandboxed published app.

The third image, Herz-01, was using scene 1 instead of scene 3. This means that just one spring is used in the computation. The difference was FaktorEQ. The published app will run all computations on the GPU. It does not contain the code which evaluates the model on the CPU. And that is why FaktorEQ was always the same, and did not change as it usually does when another scene is selected.

FaktorEQ will be used for scaling. It makes sense when it is different for each scene. But it does not have to. There are several ways to compensate for it, to achieve it through different means. The test sample worked perfectly with the published app, but I now remember that I had tweaked it to do so. Obviously, in the future the samples should work everywhere the same. I changed the code and it will be in the next published version.

There have been at least two reasons in the past why samples needed to be tweaked, the other one being the projection. Delphi does not do orthographic projection out of the box. And when you run without a patched version of the FMX library, you do not have it. It is desirable to sometimes run without the patched FMX units. So the issues with a unified set of example data is not always related to internal barriers.

I am starting out with GitHub and I am keeping it simple and I expect to make mistakes. Today I uploaded a new sample data file, Herz-02.txt, instead of changing Herz-01.txt to reflect the latest good version. The odd situation is that I have uploaded an image to the sample-data repository which does not work perfectly with the current app.

Resolution: I will recycle the Herz-01 sample in the future to show another good-looking heart, and maybe add the corresponding image to the imprinted sub-directory.

So, good news, the app is out, the sample data repository is up and running, and a future version of the app will be even better. You can use imprinted samples and you can create them. Try them out, they are cool.

(The text data files are smaller though.)