Welcome, Guest. Please login or register.

Author Topic: GLFW (OpenGL toolkit) port for AmigaOS  (Read 15198 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW port for AmigaOS
« on: May 13, 2003, 03:13:59 AM »
Hi there,

I have something that might interest you. I have written a C++ based portability layer (still in development) that encapsulates many things useful for your project

1) Applet based startup (your application is a class and you don't need to define main() anymore). It gets instansiated once all the critical OS initialisation is done.
You can inherit components of the portability system that way (or use a compositional design pattern if you prefer).

2) High performance memory class

3) Abstract input handling - you inherit InputFocus and override only those virtual functions you require as input handlers.

4) Kernel service classes, eg: Thread class, Lockable class, Timers, MilliClock etc.

5) Graphics abstraction layer - Colour, Surface, Display, Rasterizer (using Warp3D), and other classes such as input focus to get input back from a display etc. as well as standard subclasses, eg Window, Screen etc..

6) Audio classes (fell a bit behind there).

For example, if you wanted to write an application that uses a double buffered screen (with input handling)

class MyApplication : private AppBase, FullscreenDB {

};

Does 90% of the work for you :-)

The project is aimed at supporting a lot of platforms though the Amiga68K version is the one most in deveopment just now.

If youre interested, drop me an email...

-edit-

Oh, and the whole thing uses pretty lightweight interfaces because I designed it for multimedia / gaming rather than building in tons of excess bloat in the event some of it might be useful :-)

int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW port for AmigaOS
« Reply #1 on: May 13, 2003, 12:59:44 PM »
Ah well, I see you have your stuff covered nicely. It was just a thought since we were obviously going for the same goals (nearly)...

As for portability, my layer does work nicely on other platforms (but as I said theyre slow to catch up because I don't work on them much) principally because it doesn't use any 'wobbly' C++ features internally, namely

namespaces
exceptions (you should see the problems that gave in a multithreaded environment)
RTTI
templates

Actually there are some *very* simple templates (things like protected inegers), nothing that didn't compile everywhere I tried.

However, it is a link library so I don't get quite the compactness you do. I am thinking to make a shared library implementation of the internals that will shrink the link library to a stub but that will take some work (and redesign of the class internals).

Creating an application is extremely simple and fairly robust - if some essential stuff cant be set up, your actual application is never even created.

Anybody whos ran my tests for the 3D card has already used it...twice :-)
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW port for AmigaOS
« Reply #2 on: May 13, 2003, 03:14:32 PM »
Yup, this is a problem, but only for 68K libraries as far as I know.

The problem is that standard library interfaces work (as I'm sure you know) by passing arguments in registers, either d0-d7 for ints etc and a0-a3 for pointers.

Passing FPU stuff in fp0-fp7 isn't supported IIRC.

The simplest approach is to pass floats / doubles by reference.

-edit-

I think you can also pass 32-bit floats in registers d0-d7 too. Youd need something like

void myfunc(REGD0 float fval);

where REGD0 is some convenient macro abstraction for specifying a register argument using d0...

-endi edit-

This only affects your actual library interface to the outside world. Normal C conventions apply to internals so there's no reason why functions internal to your library can use floats/doubles as args normally.

-another edit-

Incidentally, are you using MesaGL? You may want to look at MiniGL for performance under current apps. It's a bit of a whopper static link library though...
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #3 on: May 18, 2003, 09:35:37 PM »
Hi marcus,

The only problem would be if you mixed up a normal thread with one one of your GLFW ones and proceeded to use it as if it were.

My Threadable service class (base for all threaded objects, wraps a task) uses the same strategy as you describe.

On creation of the task I set tc_UserData to point to the actual Threadable class that encapsulates it.

To avoid problems in cases where other threads that use the same field for their own purposes, the Threadable class has an identifer member value inside used to identify it as a 'Threadable' class object. This is basically an integer which must match a defined value 'IS_THREADABLE'

So, to get the current Threadable object for which the code is executing uses a static function thus

Threadable* Threadable::getCurrent()
{
  Task* who = FindTask(NULL);
  if (who)
  {
    // this is the dodgy bit, we cast to Threadable and see if the id matches
    Threadable* thread = (Threadable*)(who->tc_UserData);
    if (thread && thread->identity == IS_THREADABLE)
      return thread;
  }
  return 0; // not a Threadable thread
}


I certianly haven't ran into any problems with this approach. You could just put a check like the one above use identifier field in your structure. If you ever do somehow handle a thread which is not one of your GLFW ones, it can be differentiated easily by seeing if the data pointed to has this identifier.

Works for me :-)
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #4 on: May 18, 2003, 10:21:54 PM »
Incidentally, I should point out that I rarely need to determine the current thread using the method I mentioned since 99% of the time the task is running within the context of the Threadable object itself...

For code calls external to the Threadable object (and not given a reference to it) to the method described is essential and works a treat...
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #5 on: May 19, 2003, 01:03:14 PM »
Quote

marcus256 wrote:
That's a good solution (at least as good as it gets, I suppose).


Yeah. It's relatively simple and no way you can actually get access to a thread which isnt one of your own - the function just returns null if the thread that called it isn't one of your custom kind.

Quote

Why? Thread aware window management!

The idea is very similar to OpenGL per thread rendering contexts (one context per thread - transparent from a coders point of view). For instance, I will have a function called glfwBindWindow(), which binds a window to the currently calling thread. All window operations will apply to that window. Since different threads can work on different windows, I need to store this "window binding" information in an easily accessible thread private area - my GLFW thread structure, pointed to by tc_UserData, is the obvious choice.


That should work. According to the RKM, tc_UserData is entirely free for the programmer to use for pointing to task specific data that is meaningful to them. IIRC, exec pays it no attention whatsoever.

My only point is that multithreaded rendering doesn't do much performance wise since at the end of the day you only (usually) have one hardware rendering device that needs to be exclusively locked. I guess that's not your point anyway - most uses for multithreaded code are to simplify design rather than a speed optimisation.

Hwever, I did find one use. I made a Threadable Rasterizer class (still in development) that has a double buffered vertex array / command queue. The rendering calls fill one buffer whilst the previous one is being rendered by the internal thread.
In this instance there was an overall perfomance increase, principally because the rendering code often has to wait for the hardware to complete an operation before it begins a new one. By running it as a seperate task at a lower priority than the parent, when it's waiting the parent gets the cpu to continue working on other stuff realtively unimpeeded.

Quote

I can still use the "fool-proof" check that you described, but that means that using non-GLFW threads will limit the use of GLFW functions (e.g. you can't open a window from a non-GLFW thread).


I should point out that I do have a singleton MainThread class (derived from Threadable) that wraps the main thread of execution. That way the main process is seen as Threadable to the rest of the system.
You could probably manage something similar.

Quote

I wish AmigaOS had something similar to POSIX pthread thread-specific data keys or Win32 thread local storage (TLS), which are basically indexed arrays of tc_UserData that you can dynamically allocate, per process. I suppose this does not work well without a  proper process class though...


That's the beauty of Threadable. You can extend it however you wish :-)



But why not just create a structure thus...

typedef struct {
  long identity;
  size_t numDataHandles;
  void* dataHandles[1];
} GLFWThreadLocalStore;


...and allocate that dynamically with a function eg :


GLFWThreadLocalStore* CreateLocalStore(size_t numHandles)
{
  GLFWThreadLocalStore *tls = (GLFWThreadLocalStore*)malloc(sizeof(GLFWThreadLocalStore)+(numHandles-1)*sizeof(void*));
  if (tls)
  {
    size_t n;
    tls->identity = IS_GLFWTHREAD;
    tls->numDataHandles = numHandles;
    for (n=0; ntls->dataHandles[n] = 0;
    return tls;
  }
  return 0;
}


When you need to resize the TLS, you can basically use a standard library function like realloc() to preserve whats in there..

If youre interested I can send you the source code to my kernel classes that will allow you to see how I overcome some of the problems you describe.
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #6 on: May 19, 2003, 01:04:31 PM »
....er and I forgot to say make tc_UserData point to your GLFWThreadLocalStore object :-D
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #7 on: May 19, 2003, 05:16:04 PM »
oops..wrong post
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #8 on: May 20, 2003, 11:18:39 PM »
Hi marcus,

-edit-

Is it a problem that the application running on GLFW cant use the tc_UserData if you use it? I thought the point was to avoid system dependencies. Just use your own GLFW threads within a multthreaded GLFW program, surely. As I see it, your GLFW threads are an interface. If you add your own TLS to it then the users of your framework will just use that instead.
-end edit-

Signalling in my  system is realitvely straightforward. Since I have threadable objects, as opposed to just seperate threads running through some arbitrary code, I just perform a method for that object. If that method changes internal data, the thread will be aware of that automatically (having acces to the protected level internals). Methods which require synchronised access can simpy use a Lockable object that is bound to the internal thread (you can't lock it until the internal thread is done with it). Lockable is a service class that encapsulates the Semaphore mechanism.

The actual Amiga task, running within the context of the object, can always see the (protected) state information. Due to this, the only real signalling I need is to be able to go to sleep and wait for an event, or a time out. The theadable service provides a delay timer feature too, using the DelayTimer class (itse;f an encapsulation of the timer.device).

There is a sleep() method for theadable objects that actually uses the amiga Wait()/Signal() system.
So the internal thread can literally go to sleep. When you then kick the object by invoking the wake() method, the appropriate exec level signal is sent to the internal task which is then woken up and carry on.

So really I don't use a lot of different signalling, just sleeping and waking. All other state info is actually part of the object definition. There is also a shutdown signal defined which basically tells the internal thread to remove itself. The internal thread code can simply call the method that checks for a shutdown call and then do whatever is required to finish and exit.
It does this(cleanly) by a return from the run() method.
The thread which invoked the stop() method (which may be part of the destruction for example) is then forced to wait on the internal thread to finish.

The only other thing is that a call to shutdown() will wake up the thread if it is waiting for something already. This allows threads to respond quickly to getting told to finish up.

It's a robust system and the interface ensures that youd have to especially set out to break it in order to screw it up.

Quote

Of course, critical sections (Forbid/Permit) is used wherever necessary


Try to avoid this. If you can, use semaphore locking for shared resources, its much friendlier - especially if your going all multithreaded...
The only place I use this is inside the start() method that creates the task. With task switching momentarily disabled, I write the tc_UserData to point to the object and thats it.
I don't use it anywhere else and would rather avoid it all together. If you ever do a WarpOS version you'll see there is no ForbidPPC()/PermitPPC()...

-threaded graphics-

Agreed. In most cases a multithreaded approach to rendering on a single processor system is pointless.

However, the double buffered rasterizer I wrote works reasonably well because on the current hardware, rendering takes time and does force the calling code to wait (we are talking direct Warp3D level stuff here, not OpenGL). Only the simple pre-v4 Warp3D calls are asynchronous. The v4 vertex array calls (which I use for efficiency/flexibility reasons) are not (well there may be some parallelism at the hw level). The cache thrashing issue isn't much of a problem in my code since the buffers aren't very large anyway.

So, in my case whilst drawing isnt physically any faster, the setup stage can continue so time is not wasted. The threaded double buffering adds the asynchonicity you would expect from a 'decent' OpenGL implementation.

I don't have a high level 3D system apart from a simple transforamtion / shading engine. I have no interest in trying to compete with OpenGL :-)

Anyway, if you use multithreading under Windows, you'll love it on AmigaOS. Task switch times are miniscule :-)
int p; // A
 

Offline Karlos

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16867
  • Country: gb
  • Thanked: 4 times
    • Show all replies
Re: GLFW (OpenGL toolkit) port for AmigaOS
« Reply #9 on: May 21, 2003, 02:39:36 PM »
Hi marcus,

-edit-

Pity we are never online at the same time!

PiR is right - a linked list is more efficient for this, I would say...
-end edit-

About the signalling policy. What I mean is, most of the time, the thread is running inside the context of the threadable object. Like the Thread interface in java, for example.
So, say I write code for a threadable object that waits for a member of that object to change value. The code would be something like

void MyThreadableObject::waitForValueChange()
{
  int oldvalue = value;
  while (oldvalue == value)
    sleep(); // indefinate until wake() or stop() called
}

Note sleep() is a simpliication. The real method is idle(uint32 millisecs, bool ignoreWake, bool abortIdle, SysSignal trigger);

..but that confuses the example slighty. Using a delay time of 0 ms is forever...
This pethod is called by the internal thread. I may write a public method like this

void MyThreadableObject::setNewValue(int v)
{
  value = v;
  if (isSleeping())
    wake();
}

And thats it. As soon as I call setNewValue() and the value I pass is different from the internal one, the internal task, if already asleep, will woken up.

As for delays. Don't use Delay() - it's bobbins for accuracy.

Use the timer.device and wait on that. I have a DelayTimer which is derived from MilliClock. It gives millisecond delay accuracy. I could easily mmake this finer but the MilliClock implementation has to work on lots of platforms, not all of which have the microsec resolution available to the amiga.
int p; // A