I looked at how GLEW does it, and it is very similar to what Jameson proposed.. The difference from Jameson's proposal is that the if statement is around something like HAVE_EXT_### variables, rather than comparing the function pointer to NULL. The function pointers and the HAVE_EXT_### variables are all set in the initialization/loader routine. This allows fallback behavior depending on the available functionality in a given context.
On Thu, Apr 3, 2014 at 5:23 AM, Simon Danisch <[email protected]> wrote: > By the way, this solution doesn't deal with the problem, that the OpenGl > package needs to expose different sets of functions depending on the OpenGl > version, which can be only determined after the context creation. > Or is this bad style and one should just include all functions, leaving > unsupported function with C_NULL? But it would be a difference of a couple > of thousand lines of code. > > Its actually not trivial, as a package that doesn't create an opengl > context might include a particular version, independent of the version that > is actually used by the package, that deals with creating the opengl > context. > > Also, if we already start sacrificing performance, we could just go back > to the trivial solution, which is to put the getprocaddress into the ccall. > This at least guarantees, that one always gets the right pointer, even if > the context gets switched, destroyed or changed... > > On Apr 3, 2014 10:48 AM, "Mike Innes" <[email protected]> wrote: > > > > Ok, that's interesting. This is obviously a pretty clever macro, because > it's not at all obvious that it should run as quickly as it does. > > > > But, I'm still not sure I understand why that solution is necessarily > the least brittle and most correct. As I see it, we have three potential > solutions here, which in order of most elegant / least performant are (1) > getFuncPointerM memoization, (2) @getFunctionPointer memoization, (3) @eval > inlining. (1) and (2) are exactly the same amount of typing, (3) a few > characters more. (3), I would argue, is more clear about what it does and > why it's fast, but perhaps your opinion will differ. > > > > Without clear reasoning, drawing a line of "correctness" between (2) and > (3) seems pretty arbitrary. I can see why you might argue that only (1) is > correct, since it's evidently easier to understand and maintain. But what > exactly makes (3) so much worse than (2), objectively speaking? > > > > > > On 3 April 2014 09:21, Simon Danisch <[email protected]> wrote: > >> > >> Can someone explain me what exactly is brittle about it? > >> I don't like the solution too much myself, as it disguises things a > little. > >> > >> But I don't know Julia ( or macros and programming concepts in general) > well enough to see the bad side effects. > >> At least it gets the job done, which is to generate a function body > that only consists of a ccall with an inlined function ptr. > >> > >> Or is this not desirable? > >> > >> On Apr 3, 2014 5:19 AM, "Jameson Nash" <[email protected]> wrote: > >>> > >>> As I indicated, I used the macro to make the code look neater. I could > >>> have expanded it (see below). But just as functions help DRY code, > >>> macros help DRY typing. Memoizing using a dict isn't much better than > >>> calling dlsym, which is also some form of dict (and may even benefit > >>> from better memory localization). I am doing almost the same (using > >>> the module global namespace as a dictionary), but I am cheat because I > >>> can inform the compiler that the result of this dictionary lookup is > >>> static, which lets it emit a direct entry into the module global > >>> lookup table, at essentially negligible runtime performance impact > >>> (one mov and one jmp instruction more than directly ccall'ing a > >>> pointer). > >>> > >>> Using eval to do inlining is a brittle usage of both eval and > >>> inlining. it is nice to have the computer run things fast, but it is > >>> better have it do them right :) > >>> > >>> const glGetString_func_pointer = C_NULL > >>> function glGetString(name::Uint16) > >>> global glGetString_func_pointer > >>> if glGetString_func_pointer::Ptr{Void} == C_NULL > >>> glGetString_func_pointer::Ptr{Void} = > getFuncPointer("glGetString") > >>> end > >>> ccall(glGetString_func_pointer::Ptr{Void}, Ptr{Cchar}, (Uint16,), > name) > >>> end > >>> > >>> On Wed, Apr 2, 2014 at 9:59 PM, Mike Innes <[email protected]> > wrote: > >>> > I agree entirely that macros and eval should be avoided if possible > - so why > >>> > have you used them for memoization? > >>> > > >>> > > >>> > const func_pointers = Dict{String, Ptr{Void}}() > >>> > > >>> > > >>> > getFuncPointerM(name) = > >>> > > >>> > haskey(func_pointers, name) ? > >>> > > >>> > func_pointers[name] : > >>> > > >>> > (func_pointers[name] = getFuncPointer(name)) > >>> > > >>> > > >>> > Perhaps I'm missing something, but that seems equivalent and a lot > neater to > >>> > me. The benefit to your macro is that it's faster, which is exactly > the > >>> > reason to use eval in Simon's case. > >>> > > >>> > If retrieving a pointer carries a significant overhead compared to > inlining > >>> > it (which may well be a concern in an OpenGL library), then using > eval to do > >>> > that inlining is perfectly justified. > >>> > > >>> > > >>> > On 3 April 2014 02:13, Jameson Nash <[email protected]> wrote: > >>> >> > >>> >> A delayed macro is just a function call. Don't make you life > >>> >> complicated by trying to use one in place of the other. > >>> >> > >>> >> Using macros and eval can get you into a lot of trouble by helping > you > >>> >> write brittle code. They allow you to confuse compile and run time, > >>> >> even though Julia does make a strong distinction between them. Avoid > >>> >> using them. > >>> >> > >>> >> The correct way to write this is using memoization. We can use a > macro > >>> >> to make this look neater. This one is copied from PyCall: > >>> >> > >>> >> macro getFuncPointer(func) > >>> >> z = gensym(string(func)) > >>> >> @eval global $z = C_NULL > >>> >> quote begin > >>> >> global $z > >>> >> if $z::Ptr{Void} == C_NULL > >>> >> $z::Ptr{Void} = $(getFuncPointer(esc(func)))) > >>> >> end > >>> >> $z::Ptr{Void} > >>> >> end end > >>> >> end > >>> >> > >>> >> glGetString(name::GLenum) = ccall(@getFuncPointer("glGetString"), > >>> >> ...., ...., name) > >>> >> > >>> >> > >>> >> A good macro should be equivalent to a pure function: regardless of > >>> >> when or how it is run, or how the program state changes, it must > >>> >> return the same result given the same inputs. In this case, it > returns > >>> >> a static variable and a method of retrieving the actual function > >>> >> pointer. Note that it does not actually look up the function pointer > >>> >> or return it -- that cannot be done until runtime. > >>> >> > >>> >> eval is actually just a macro call in disguise, so the same > guidelines > >>> >> apply. This also happens to demonstrate an appropriate (safe) use of > >>> >> eval. > >>> >> > >>> >> > >>> >> PS. this code should be causing a compile-time deprecation warning > >>> >> (because of the constant there), I'll have to investigate why that > >>> >> code is not working anymore: > >>> >> return top(ccall)(Ptr{Void} > >>> >> @0x00007f402e6dadc0,Ptr{Int8},(Uint16,),name::Uint16,0)::Ptr{Int8} > >>> >> > >>> >> On Wed, Apr 2, 2014 at 11:56 AM, Isaiah Norton < > [email protected]> > >>> >> wrote: > >>> >> > I stand corrected. > >>> >> > > >>> >> > On Wed, Apr 2, 2014 at 9:50 AM, Simon Danisch <[email protected] > > > >>> >> > wrote: > >>> >> >> > >>> >> >> Ah, the links are all the same... > >>> >> >> Output for Mikes solution: > >>> >> >> native: > https://gist.github.com/SimonDanisch/6270c01a6ea881877c4f > >>> >> >> llvm: > https://gist.github.com/SimonDanisch/612e8b08d915d188c4d5 > >>> >> >> > >>> >> >> > >>> >> >> > >>> >> >> > >>> >> >> 2014-04-02 15:46 GMT+02:00 Simon Danisch <[email protected]>: > >>> >> >> > >>> >> >>> @ Mike > >>> >> >>> Good question. Well, I definitely would wish for a simpler > solutions. > >>> >> >>> But as far as I understand the situation, every video card > vendor > >>> >> >>> makes > >>> >> >>> his own OpenGL implementation. > >>> >> >>> Also, even on one computer, you can have more than one rendering > >>> >> >>> context. > >>> >> >>> This means, the correct function pointers can be just queried, > after > >>> >> >>> one > >>> >> >>> creates a particular context. > >>> >> >>> > >>> >> >>> > >>> >> >>> So I looked into the machine and llvm code and its pretty clear! > >>> >> >>> The version inspired by Mike generates a lot shorter llvm and > machine > >>> >> >>> code. > >>> >> >>> It also doesn't matter if I call glGetString before inspecting > the > >>> >> >>> machine code. > >>> >> >>> > >>> >> >>> My test program for Isaiah's solution: > >>> >> >>> > >>> >> >>> #glTest.jl > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> using GLUT, OpenGL > >>> >> >>> > >>> >> >>> glutInit() > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA | > >>> >> >>> GLUT_MULTISAMPLE | GLUT_ALPHA) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> glutInitWindowPosition(0, 0) > >>> >> >>> glutInitWindowSize(1,1) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> window = glutCreateWindow("dummy") > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> println(code_llvm(glGetString, (Uint16,))) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> println(bytestring(convert(Ptr{Uint8}, glGetString(0x1F02)))) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> println(code_llvm(glGetString, (Uint16,))) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> #OpenGL.jl > >>> >> >>> module OpenGL > >>> >> >>> > >>> >> >>> function getFuncPointer(name) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> ccall( (:glXGetProcAddress, "libGL"), Ptr{Void}, > (Ptr{Cchar},), > >>> >> >>> name) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> end > >>> >> >>> glGetString(name::Uint16) = ccall(getFuncPointer("glGetString"), > >>> >> >>> Ptr{Cchar}, (Uint16,), name) > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> > >>> >> >>> export glGetString > >>> >> >>> end > >>> >> >>> > >>> >> >>> > >>> >> >>> Output: > >>> >> >>> https://gist.github.com/SimonDanisch/777bfc7783f9e96818f4 > >>> >> >>> > >>> >> >>> Output for Mikes solution: > >>> >> >>> native: > https://gist.github.com/SimonDanisch/777bfc7783f9e96818f4 > >>> >> >>> llvm: > https://gist.github.com/SimonDanisch/777bfc7783f9e96818f4 > >>> >> >>> > >>> >> >>> > >>> >> >>> Am I missing something? > >>> >> >>> > >>> >> >>> > >>> >> >>> Am Dienstag, 1. April 2014 14:30:12 UTC+2 schrieb Simon Danisch: > >>> >> >>>> > >>> >> >>>> Hi, > >>> >> >>>> I'm working on the OpenGL package and I want to make it finally > >>> >> >>>> usable > >>> >> >>>> in a nice and clean way on all platforms. > >>> >> >>>> The problem is, that one needs pointer for the GL functions, > which > >>> >> >>>> you > >>> >> >>>> can only get, after initialization of the OpenGL context. > >>> >> >>>> But initializing the context and creating a window shouldn't > be part > >>> >> >>>> of > >>> >> >>>> the OpenGL package. > >>> >> >>>> > >>> >> >>>> So I tried two different approaches, which both seem to have > their > >>> >> >>>> downsides: > >>> >> >>>> > >>> >> >>>> 1. > >>> >> >>>> Initialize OpenGL context when including the OpenGL package > >>> >> >>>> This is bad, because this makes the OpenGL package dependent > on some > >>> >> >>>> third party OpenGL context creation library. > >>> >> >>>> > >>> >> >>>> 2. > >>> >> >>>> Load the functions later with a loading Function. > >>> >> >>>> Bad, because the function definitions are not visible for any > other > >>> >> >>>> module, that relies on the OpenGL package. > >>> >> >>>> > >>> >> >>>> My ideal solution would be, to evaluate a macro when the > function is > >>> >> >>>> called and not when the module is included. > >>> >> >>>> Like this, I can define all the OpenGL functions already in the > >>> >> >>>> OpenGL > >>> >> >>>> module, and when you call them the first time, > >>> >> >>>> the right function ptr gets inserted into the ccall, or an > error is > >>> >> >>>> raised, when OpenGL context is not initialized. > >>> >> >>>> > >>> >> >>>> this could look like this: > >>> >> >>>> > >>> >> >>>> module OpenGL > >>> >> >>>> > >>> >> >>>> macro getFuncPointer(name::ASCIIString) > >>> >> >>>> return getProcAddress(name) > >>> >> >>>> end > >>> >> >>>> > >>> >> >>>> glGetString(name::GLenum) = > ccall(@getFuncPointer("glGetString"), > >>> >> >>>> ...., > >>> >> >>>> ...., name) > >>> >> >>>> export glGetString > >>> >> >>>> end > >>> >> >>>> > >>> >> >>>> using OpenGL > >>> >> >>>> ...create OpenGL context > >>> >> >>>> #define getProcAddress > >>> >> >>>> global const getProcAddress = glutGetProcAddress # If using > GLUT for > >>> >> >>>> GL > >>> >> >>>> context creation > >>> >> >>>> #call gl Functions > >>> >> >>>> glGetString(GL_VERSION) > >>> >> >>>> > >>> >> >>>> Any ideas how to do this in a clean way? > >>> >> >>>> > >>> >> >>>> > >>> >> >>>> Cheers, > >>> >> >>>> > >>> >> >>>> Simon > >>> >> >> > >>> >> >> > >>> >> > > >>> > > >>> > > > > > >
