RoosterDragon / Desktop-Ponies

Desktop Ponies in .NET [The Original!]
183 stars 32 forks source link

DPE as a backend? #2

Closed hidefromkgb closed 9 years ago

hidefromkgb commented 10 years ago

Exactly as it says on the can.

I propose the overall structure to be as follows: [1] At start, DPE allocates rendering threads and output windows, one window for each renderer available, then reads the whole base into memory (~80 MB) and passes control to DP. [2] DP, according to user`s request, chooses which ponies («unit libraries» in DPE terminology) shall be visible, and then controls the execution via main window, options list and context menus. [3] It is still unclear for me how DP and DPE shall interact, but I think it might be some set of handlers on both sides, which can be registered and unregistered: for example, context menu handler registered by DP and called by DPE, or options update handler, registered by DPE and called by DP.

From the «binary» point of view, I plan DPE to be several separate libraries: DLL for Windows, SO for Linux and DYLIB for Mac, so it would be up to DP to determine the OS and choose which library to load.

This is how I see it. Feel free to correct and extend =)

RoosterDragon commented 10 years ago

So we have somewhat different views on this. I was thinking DPE would more more along the lines of something that could more easily fitted into the current interfaces:

The core of the current renderers is a draw method that takes an ordered collection of 'sprites' - a sprite basically boils down to an image path, location and a size. The renderer has to draw that image at that location and size but how it does that is entirely up to the renderer. How it handles images is also its choice - the path is just the key and it's up to the renderer to handle loading it in a format that it likes.

DPE has a sucky implementation of ponies and DP has a sucky implementation of renderers. So I think it makes more sense for DP to handle ponies and DPE (or just DE I suppose) to handle rendering.

You do lose - or perhaps have to work around - some very specific optimizations like the sharing of mirrored images or scaling down by 2x to use less memory. But DP manages to implement the scaling down by 2x at runtime and fits everything into ~100 MiB (it keeps the main menu in memory which is another 30). If it could do some runtime mirroring checking that'd probably knock off 30-40.

So anyway, does treating DPE as a generic renderer make more sense to you, or did you actually want to handle the pony aspect in some fashion at your level?

hidefromkgb commented 10 years ago

Well, that sounds definitely reasonable. I'd really hate to reimplement interactions myself and maintain them in the future. I'm okay with the scheme you propose — it means less work for both of us ^_^

Now for the implementation. AFAICS, we only need three functions to link DP to DPE and one function to link DPE to DP. First three are «load animation pair» (further addressed to as LAP), «render display list» (RDL) and «render animation to specified memory block» (RMB). The remaining one is context menu callback (CMC).

LAP shall accept two null-terminated UTF8 strings (one for right-sided animation, one for left-sided), and a pointer to the structure to hold resulting unique handles and corresponding animation sizes and frame counts. The return value shall be greater than zero in case of successful loading.

RDL might have a sole parameter — let it be a pointer to the list of sprites DP needs to render, assumed to be sorted by vertical position in descending order. I'm not sure if such pointers can be passed outside of .NET environment, but if they can, that's great. RDL would read animations handles (planned to be just pointers to DPE internal animation structures) and positions to render at. Each frame, the position of the sprite dragged by the user will be updated.

RMB will accept an animation handle, a memory block that fits its dimensions, and a frame number. Also not sure if this method of drawing is possible in .NET.

CMC will be called each time the user presses a mouse button over some sprite. Since calculations are done inside DPE, it needs to be able to tell DP which sprite is selected. That being said, it seems that DP also needs to have handles to sprites, which will be re-read when a new frame is drawn.

P.S.: I don't think I'll lose downscaled or shared mirror images: mirroring test can also be done on the fly, just like downscaling. Ah, one more thing: if the «left-sided» filename is empty, or the file does not exist, DPE will assume that the left-sided sprite is a copy of the right-sided original. All pixel data will be shared, so DP can safely disregard it in case it's not needed. This approach may lead to disk space advantage: removing lefts won't break anything.

RoosterDragon commented 10 years ago

C# can manipulate raw pointers just fine - no issues there.

I don't currently have any concept of animation pairs by the time I reach my rendering abstraction - it just considers a single animated image at a time. I can see that it might be sort of useful - but if I only provided a single path (you don't need to care if it's left or right) would that be alright for now or is the pair of paths super important?

At my level I have access to image sizes. However I don't have frame counts and durations (without loading the whole GIF anyway). At my level I keep a time index for the frame I wanted rendered. My animation stuff determines the correct frame index to use.

You have a choice - LAP can return the frame durations, count, and a pointer and I can do the math and pass a frame index to RDL. Or you can return an opaque pointer to the animation from LAP and I'll give you a time index (say total milliseconds) and you can do the math at your level.

I don't need to know about sprites being dragged specifically. If you can provide methods that returns the cursor position and callbacks for when a mouse button is clicked that is easier for me because that's how my dragging logic is wired up right now.

I'm not sure what you want RMB to do. I just wanted something like you describe with RDL - I'll tell you what to render and where and you just do that however it needs to be done. What was your intended use for RMB exactly?

It sounds like we have some quite different ideas about how the context menus should work. But they're not important at the moment so let's just ignore them for now.

I'm also thinking we might need some functions for opening/closing the interface, and probably something for setting the desired window position and size too.

Pony speech should also be considered at some point, but we can leave that off immediately.

hidefromkgb commented 10 years ago

would that be alright for now or is the pair of paths super important?

I consider it to be really important in terms of loading speed. Having a pair of paths which for sure belong to different directions but represent the same animation would notably simplify mirroring tests.

Or you can return an opaque pointer to the animation from LAP and I'll give you a time index

That'll be better, since it better complies with low-coupling principle.

If you can provide methods that returns the cursor position and callbacks for when a mouse button is clicked that is easier for me because that's how my dragging logic is wired up right now.

Exactly the job of CMC. It also may be called when no button is pressed, of course, to let DP know which sprite has to be set to mouse-over state.

What was your intended use for RMB exactly?

Main menu. For performance reasons, RDL won't draw anywhere except its output window, so I thought it would be better to introduce a special function that could actually accept some output destination.

I'm also thinking we might need some functions for opening/closing the interface, and probably something for setting the desired window position and size too.

An maybe also some supporting functions like «wait for LAP to finish loading», since LAP works asynchronously.

RoosterDragon commented 10 years ago

OK I can set up animation pairings.

I had my cursor stuff slightly wrong, I'd like a function of functions for getting the current mouse state (location and buttons pressed). As well as a callback function for when clicks occur. CMC covers the callback but some "get the current state" functions are also useful.

I won't need RMB. The main menu just handles drawing the images itself.

I will definitely need some sort of signal for when LAP finishes an async load.

hidefromkgb commented 10 years ago

I will definitely need some sort of signal for when LAP finishes an async load. <…> some "get the current state" functions are also useful.

Got it, there will be.

I won't need RMB. The main menu just handles drawing the images itself.

But… why? That's just aditional memory space and additional loading time. And it`s also not good from the p.o.v. of architecture ._.

P.S.: finally managed to install a vanilla MacOS 10.6 on my VirtualBox. To my surprise, at least on non-apple hardware, MacOS appears to be awful >_< It`ll take some time for me to get acquainted with XCode and make a Mac wrapper in Obj-C.

RoosterDragon commented 10 years ago

RMB would be horrible. Where am I supposed to get this block of native memory you want to draw into? I've got two different main menus that work under two different .NET runtimes on three different platforms using two different UI frameworks. Who knows if the use a consistent native format across all those different possibilities. Who knows if I can even reliably get a pointer into a chunk of data for drawing? I sure don't. I'll just use the existing API they provide that works without any headaches.

The memory and loading time concerns aren't really worth the hassle either. I think the images might account for something like 5 megabytes and 10% of the initial loading time.

I'm lazy and the current stuff works. Sue me :P

I'm a bit busy for the moment, but once you have some concrete headers I'll see if things can be glued together at some point.

hidefromkgb commented 10 years ago

Well, OK then. Firstly, I'll add OpenGL support to what I've got, and then make DPE a set of dynamic libraries. As usual, that may be anything but fast.

two different main menus that work under two different .NET runtimes on three different platforms using two different UI frameworks

I think somewhere in the future, we could transfer some UI to DPE.

RoosterDragon commented 10 years ago

Just pinging you to ping me back when you're getting close to something you think can start being integrated.

I'd like to start super-simple. Like if we can get DP to load up DPE and display any ponies and even if it worked only on Windows, that'd be great. Then we can start building up from there.

No rush or anything, just wanted to be ready. I'm pretty stoked for this.

hidefromkgb commented 10 years ago

Okay. When I finally finish all things I planned, like frame-wise texture banks (and not animation-wise, which restricted minimal texture size to be 2048×2048) implemented today, I`ll split DPE in a set of libraries and «example» executable modules loading these libraries. If C# allows static loading of different variants of a single library on different systems, please let me know, so I won`t load them dynamically.

RoosterDragon commented 10 years ago

According to the mono docs, it will attempt to look up the correct library for the platform at runtime:

If you have control over the library name, keep the above naming conventions in mind and don't use a platform-specific library name in the DllImport statement. Instead, just use the library name itself, without any prefixes or suffixes, and rely on the runtime to find the appropriate library at runtime. For example:

 [DllImport ("MyLibrary")]
 private static extern void Frobnicate ();

Then, you just need to provide MyLibrary.dll for Windows platforms, libMyLibrary.so for Unix platforms, and libMyLibrary.dylib for Mac OS X platforms.

I assume that's what you're after here?

hidefromkgb commented 10 years ago

Exactly!

RoosterDragon commented 10 years ago

I am playing around with this. Once I get enough integration working so it does something that isn't just crashing or whatever I'll let you know.

hidefromkgb commented 10 years ago

Great! I can provide you with the stub library in case you need it. When called, its functions only print their names and parameters to ensure that the call worked as it should. P.S.: still searching for dynamic loading approach with the most adequate tradeoff between RAM size and CPU time needed for reallocation.

hidefromkgb commented 10 years ago

OK. Now it`s possible to add new sprites and change the resulting display-list size from within UpdateFrame(). Currently, doing this means deleting everything and recreating it from scratch. This will be polished later, but all changes will stay inside DPE, so you won`t have to further modify anything if you make it work with the current version.

hidefromkgb commented 10 years ago

Made some changes that radically dropped system requirements: all GLSL 1.30 code has been replaced with GLSL 1.20 compatible. As a result, performance on older machines has leapt up by a whole order of magnitude (staying the same as with 1.30 on modern GPUs). For example, a friend`s Vaio VGN-Z with GeForce 9300M GS GPU showed an 80-times — seriously, eighty times! — boost, from 16 to 1280 sprites per folder. I wonder if this does the same trick for your laptop =) GLSL 1.20 is unable to process integer textures, so the display list format has now changed. On the other side, it became way less complex, and filling the list does not require bit operations anymore.

hidefromkgb commented 10 years ago

The API has changed again, and now I`m right on track to implement a function to draw a single frame to an arbitrary memory location (may come in handy to render the main menu). When you start using DPE, please notify me of this. Until then I`ll continue messing with the API =)

P.S.: 80-times performance improvement turned out to be due to the driver update. Bah.