[ocaml-opengl] which structure for the ideal project ?
Török Edwin
edwin+ml-ocaml at etorok.net
Sat Jul 20 11:11:01 BST 2013
On 07/20/2013 12:25 AM, Florent Monnier wrote:
> Hi,
>
> I've not made any OpenGL over the last year (only 2D), but I'm planing
> to make some 3D again soon, so I'm wondering how to make things evolve
> in the right direction, and I would like to get opinions about it.
>
> So I've started a new experimentation almost from scratch, and before
> to go further too much I would like to validate or invalidate my
> choices, based on your opinions about it.
Thanks for the very detailed plan. See below for some comments, but note
that I haven't developed any real OpenGL application in OCaml, beyond some simple tests/toy applications, and the one that I've done in C was more than 10 years ago, so take it with a grain of salt :)
>
> * the gl-enums use polymorphic variants
> why?
> - standard variant are more efficient but the difference is so small
> that it can be considered negligeable.
> - polymorphic variants makes it more easy for version handling (see below)
> - possibility to remove dependencies from different modules.
> - using standard variants require that some are module packed (cf
> glMLite) and this is ugly and tedious
>
> * the gl-enums are wrapped using a pattern matching on the ocaml side
> and only an int is send to C stub
> why?
> - the C stub is then the same for every version (see below for version handling)
> - the C stub is simpler, only Int_val() to convert every gl-enum
> - more things managed on the ocaml side, means simpler code
> - people comfotable with ocaml but not C can more easily modify the code
> - see [REF-1] at the end of this email for an example of code
Keep in mind that extensions can add new enums for both parameters and returned values.
At least for the returned values it'd probably be good to have an `Unknown of int, just in case
the user has a newer OpenGL version, or an extension we don't know about.
Similarly for the return values we should include all enums allowed for that field by OpenGL versions/extensions that we know about, even if we haven't checked for their presence. I'm thinking mostly about texture format queries, etc.
>
> * each OpenGL C function stub is in its own C file
> why?
> - more easy to choose which set of functions we want to use (see
> version handling below)
> - split code in small files, easier to find a single function, or
> evetually make alternative versions for testing purposes.
This also makes it easier to handle some OpenGL 3.x+ vs 2.x differences, like gl3.h.
But if you use some wrangler lib (or do it on your own), then perhaps we don't need that anyway.
>
> * each ocaml external function also in its own file
> why?
> - version sets also easy to generate from it (see version handling below)
> - also easy to have alternative versions for testing purposes
> - for example for glBegin we have func_glBegin.{ml,mli} and func_glBegin_stub.c
These will all be automatically generated, right?
>
> * two ocaml interfaces are provided,
> one with LablGL's functions style, and
> one with native OpenGL functions names (like in glMLite and GLcaml)
> (I call the first one the Labeled interface, because it's similar to LablGL)
> (I call the second one the Garrys's interface,
> I offer an orange from organic farming for the one who guess why)
> why?
> - some people prefer the LablGL style, some people prefer native
> OpenGL functions style,
> so how to choose ? just don't choose, take them both to make everyone happy.
> - this is not an issue, the C stub is the same for both, and the way
> gl-enums are managed makes it easy to
I like the idea of choice (same for the BA vs Raw discussion below), and that one shouldn't
duplicate all the work of creating a new OpenGL binding just to support a different style.
>
> * one module for each OpenGL version,
> GL11 provides all the function set and gl-enums from OpenGL version 1.1,
> GL21 for OGL 2.1, GL33 for 3.3, and ES1, ES2, and so on...
> why?
> - Then we know exactly where we are.
> - what if I want my program to work with both OGL 2.1 and OGL 3.3 ?
> see next point
>
> * we may generate modules that are intersection sets between 2 or more
> OpenGL versions
> why?
> - we also know exactly where we are, we're writing for target that may
> use OGL 2.1 or 3.3
> - what if I want a very large version target (even every OGL version)
> ? see next point
A useful intersection might be OGL 2.1 without all functions/enums deprecated/removed in OGL3+.
>
> # If I want my program to work for large target, from OGL 1.1 up to 4.1
> then I just write several implementations, one with the fixed
> pipeline for old computers,
> and one with modern style VBO+GLSL, in each implementation we are
> sure of which
> functions we use because we only open the matching module :
> open GL11
> or
> open GL33
> then at run-time we can test which version of opengl is running
> and select the right implementation
>
> * to generate each module version, we use an over simple script,
> with over simple input data.
> why?
> - because we love KISS programming
> - please no XML hell when not needed
> - see [REF-2] at the end of this email for an example of input data
> - we generate each version module with the set of functions that
> matches this input
> - generating each gl-enum is also quite straight foreward
> - if very KISS everyone, even beginners, can modify it
> - input data that can be shared with other projects (REF-2 is actually
> taken from
> another project, but only contains functions, gl-enums still have to be added)
There is a text format .spec here (based on the ones from opengl.org) that might be easier to process than XML:
https://bitbucket.org/alfonse/gl-xml-specs/src/3c6936f1636fe3cb9cc0b4edea76dda6a22c5504/glspecs/enum.spec?at=default
https://bitbucket.org/alfonse/gl-xml-specs/src/3c6936f1636fe3cb9cc0b4edea76dda6a22c5504/glspecs?at=default
Although opengl.org has deprecated the text .spec files (so they won't get updated
for future versions/extensions) in favour of the XML registry:
https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/
If we go with some other format, then you should probably use one that is shared with another C wrangler lib to: avoid duplication of effort, find bugs more easily.
>
> * we use a wrangler lib by default, even on Linux.
> why?
> - because for portability with MS/Windows we have to
> - even on Linux it's better to. glMLite chooses an OpenGL version at
> compile time,
> it's lame for packaging purpose where when the package is compiled
> we don't know yet
> which OGL version will be on the target computer where the package
> will finally
> be installed and run.
> It was quite OK to do so until recently, because most people were
> using OGL 2.1 for a while,
> but now more and more people will use more modern versions.
> So we now need to go further.
>
> * so which wrangler lib? glew?
> http://www.opengl.org/wiki/OpenGL_Loading_Library
> http://rastergrid.com/blog/2010/03/sad-facts-about-opengl-extension-libraries/
> I'm not sure which to use, glew seems to be the most used and adviced,
> but as long as I know there is no perfect one that has all the advantages.
> Each has its pros and cons, advantages and drawbacks.
> Last time I browsed about opengl wrangler libs comparisions was maybe
> more than one year ago. I don't know if the situation has evolved nowaday.
> / Maybe the project should make it switchable at compile time,
> so each user can decide which suits the best his/her needs.
> The default recommanded for Linux packaging could be glew.
I remember that it was quite easy for glew to crash with a null-pointer dereference on Nvidia if you enabled glewExperimental, which you needed to if you wanted some newer opengl version/extensions.
The situation might've improved lately, I don't use my Nvidia card anymore so I can't tell.
Also it is a quite widely used lib, so we might as well report bugs upstream when we encounter problems with it.
For a minimal, portable opengl platform interface: glfw.org.
It has glfwExtensionSupported, glfwGetProcAddress, glfwGetVersion only, and it is quite easy
to cross compile it (version 2 at least, haven't tried version 3 yet).
But your OCaml OpenGL library needs to know which extension has which functions/enums anyway,
so might as well do the "wrangling" itself.
Another argument in favor of doing wrangling in the OCaml lib is that you'll be more consistent,
and avoid problems when your wrangler lib supports a newer version that you don't support yet,
or doesn't support the version/extension that you want (perhaps because it is a system library
that wasn't updated).
If you'd use ocaml-ctypes then this would be a nice fit, although thats perhaps a bit too unsafe.
Another alternative is gl_load from glsdk, it has per-opengl-version includes:
http://glsdk.sourceforge.net/docs/html/group__module__glload.html
> The default for people who don't know and who are compiling the
> sources could be SDL2.
> - currently my experimentation uses SDL2 wrangling mechanism
> why?
> - so that I'm using only one single lib, for windowing/user input, and
> for OGL wrangling (and even for pure 2D), and even sound
> - SDL2 provides everything we need, even the OpenGL headers.
> - SDL2 is very portable (should we even say a portability Heaven?)
> even if still a devel version (oops no now RC), compiles out of the box on both
> Linux and Cygwin (and probably the same for most used environments)
> On cygwin compiles out of the box even with MingW's toolchain
> (just do export CC="/bin/i686-w64-mingw32-gcc" before ./configure and make)
Sounds good.
>
> * should the OpenGL bindings provide highier level things?
> (like memory management, or anykind of abstraction or simplification)
> In my opinion no. Or at least not at the base modules.
> why?
> IMHO the functions should be useable at their low-level level, and so
> the project should only relay the opengl functions calls IMO.
> - But building other modules on top of the lowlevel ones or next to it
> could be a good idea IMO.
> (like I tryed in glmlite with FunGL, and Ogl_draw / Ogl_matrix)
I think there are four aspects to a higher level "thing":
1. Safe (or "debug" mode)
One thing I like about glMLite is that it has a separate type (like shader_object, texture_id)
for things that would just be an int on the C side.
This could allow to detect double-frees if some extra data would be associated with them
(perhaps only in a debug mode), and certainly prevents mixing different object types by accident.
Perhaps there could be such a higher-level "safe" module built on top of the low-level "unsafe" module.
With VBOs, etc. you no longer have to call glColor/glVertex a million times, so the performance overhead of a few additional checks per OpenGL call might be worth it.
I wouldn't mind if only the "safe" module was available though.
Initially I thought abut automatically calling glGetError, but with ARB_debug_output that is not really
necessary.
2. Convenience functions
Setting up a FBO is a PITA with the low-level calls as you need some 10+ calls, having a sane/simple interface for that would be nice.
And there are a few other things, for example SRGB surface support:
Some drivers support getting a visual with SRGB (ehrm, the closed-source ones), some only support it for a FBO (Mesa). And then you need like 3 or 4 variants:
* use SRGB visual if available
* use FBO with SRGB if available, render everything to that, and blit to main buffer
* just use a shader with the necessary (perhaps simplified) SRGB calculation
* use a (3D) texture to do some more advanced color correction and don't use the SRGB acceleration at all
And see above in my comments for automatic shader choice based on runtime OpenGL version / extensions.
3. Math functions (i.e. Ogl_matrix, vectors, matrixes, quaternions)
Not really OpenGL specific stuff, so perhaps all the necessary math stuff could go into an independent lib
(like Gg).
4. A functional interface
OpenGL is inherently imperative with a focus on setting, changing and keeping track of state.
It is necessary for a binding to provide that interface, to allow reusing knowledge from tutorials, other languages, etc.
A little improvement would be something like Batteries' with_file_in, i.e. with_shader_object, etc. to prevent resource leaks.
It would be hard to make the rest of the API more "functional" style, I think the only sane option would be something like a scenegraph, where you describe your world, how to render it, what shaders, extensions to use, etc. i.e. something similar to OpenSceneGraph but without binding to that lib.
But that could be a completely separate library from these bindings. (In fact it has to be separate
at least at the ocamlfind level).
>
> * How to manage buffers?
> / BAs?
> / Strings?
> / Arrays?
> / LablGL's Raw?
> / any other suggestions?
> pros and cons :
> - BA: nice for the absence of any dependency,
> but in glMLite I get segfault with buffers that comes from mapped memory
Bug in C binding, OpenGL driver, or something else? Would it be worth tracking down the root cause?
> BAs are very slow to alloc and finalise, which is an issue if
> we want to do it between two frames at runtime.
> - Strings: I haven't tried yet, but I seriously envisage this option
> also nice for absence of dependency for interactions with other components
> storing 16bits/32bits ints / floats in it could still be easily
> managed with a dedicated module
> Strings are faster to alloc and finalise than BAs.
Strings are moved by the GC though, so the C lib would need to make a copy for functions
that would use the buffer beyond the call itself.
> - Arrays: not possible, we need 32 bits floats not doubles,
> also not possible for ints of various size and even mini-floats
> - a dedicated module like LablGL's Raw,
> we could tailor it for what we need,
> but IMHO it should be a separated project for the dependency removing
> of other components that would have to interoperate with it.
> Other projects like pure 2D things could also use such a project.
> - the opengl functions that would need buffer management could also be
> isolated in a separated modules and then different options could be
> written and tried.
> (which is why in glmlite VertArray and VBO modules were separated
> from the main module,
> thinking that I could try something else than BAs later (which i never did)
> but is it a good idea?)
> so:
> I really don't know what would be the best option(s), maybe Raw or strings,
> but for strings would need to try and experiment with it to get a
> better opinion,
> and to see if we don't have segfaults like with BAs with video mapped memory.
> Or if we provide several options, is it a good idea? How to integrate the
> alternatives together with the other functions? Sub-modules? or different .cma?
> or suffixes?
>
> * Which type for matrices?
> I personally prefer using float arrays like I do in glMLite.
> why?
> - this is highly comfortable to not have any dependency
> @ but I do understand people willing other kind of types,
> as i would like to make something that other people would
> want to use, i would like to get the more opinions possible
> to see what most people think about it, and maybe try to
> please the majority
>
> * which type for vectors, points, colors?
> I personally prefer tuples like I now do in all my modules.
> why?
> - this is highly comfortable to not have any dependency
> - but idem than for matrices (the @ point)
>
> * Which license?
> - If I continue this experimentation further to make it reach a usable
> state it would be a liberal license.
> - but MIT, BSD, zlib/png are even not simple enough for me,
> I still don't like their conditions and restrictions that I think are useless.
> - I'm thinking to use this one below that is zlib with its restriction removed:
> """
> This software is provided "AS-IS", without any express or implied
> warranty. In no event will the authors be held liable for any damages
> arising from the use of this software.
>
> Permission is granted to anyone to use this software for any purpose,
> including commercial applications, and to alter it and redistribute it
> freely.
> """
> I still don't like the use of the word "software" which excludes the
> documentations, eventually not clear about the tests (strictly
> speaking it's not part of the software),
Each source file should have a copyright header, then it'll be clear that the tests
have the same license as the rest of the library. You can put a notice in the documentation's appendix
about its license, but see MIT license below which includes documentation explicitly.
> and also exclude images that
> could be joined for testing purpose. An image is not software.
I think you could have a README or LICENSE file in the directory of the images that explicitly states
that all images in that directory are under the <license-of-main-library>.
Then you could freely insert those images into the documentation, etc.
> I talked about it with Richard Jones (about another piece of code) who
> strongly discourages me to use a non standard license.
> What are your opinions about it?
Please use a standard license, it'll make it easier for future contributors and users of the library
(they don't need to worry about possible issues introduced by a non-standard license).
http://opensource.org/licenses/MIT
http://opensource.org/licenses/ISC
http://opensource.org/licenses/BSD-2-Clause
> Would license options be an acceptable solution?
> like in this project:
> https://github.com/blue-prawn/ocaml-xlib/blob/master/README.txt
> see the sections "LICENSE" and "ALTERNATIVE LICENSES" in the README file.
IMHO it would only complicate things and create confusion, since you want to use an already liberal license. I wouldn't go beyond dual-licensing anyway.
FWIW in the places that I worked we were allowed to link/embed BSD/MIT/Apache-licensed code in commercial software without any problems.
>
> * which build tool?
> / make?
> / ocamlbuild?g
> / ocp-build?
> - none
> why?
> - not really none, I just mean I'm thinking it could be managed in
> separated projects,
> like a set of Makefile that would share the same directory hierarchy,
> like the sources of Mesa do,
> so that tarballs could be untared in the same place a produce a
> consistent source directory ready to be build.
> Would be easier to deprecate a build if itg's outside of the main project,
> but I'm not sure I want to use something else than make yet.
You could have an oasis-based build, and a Makefile.custom for your custom makefile.
>
> ------
>
> If you have any comments, opinions, advices, suggestions, critics,
> trolls, crazy ideas about all this, please share!
> Even if you have completely other ideas about what should be the ideal
> ocaml-opengl bindings, I would appreciate to hear about it.
See above for comments.
Some things that are missing and would be nice to have:
* tests for at least the major OpenGL APIs, some of them could be automatically generated.
The idea is that you could run the tests to ensure it doesn't crash due to a bad prototype,
on various platforms with various drivers.
Also on Linux I'd very much like to have a testsuite that I could run under valgrind to find subtle
bugs.
* tests for extensions would be very much needed, to at least test that we don't crash due to a NULL dereference somewhere (if you automatically generate the bindings, then such checks could automatically be
added to raise an exception instead).
* version testing mode
Allow faking a lower OpenGL version, and removing extensions that are not core for a test mode.
Say I got an OpenGL 3.1 driver, and I want the lib to fake OpenGL 2.1 and not expose anything newer
(including functions, shader language, extensions, etc.).
This would allow to do some basic testing on the same machine to ensure that the application would
work with older OpenGL versions (perhaps by turning off effects that require newer extensions).
(Of course its not a replacement for testing with an older OpenGL implementation on another platform).
* managing shader versions
You probably need different shaders for OpenGL 3.x/2.x, and since the shader source usually has a #version and #extension anyway we could manage that automatically, depending on what you have available.
This would also interact nicely with the version testing mode above!
Something like:
val choose_shader_source: OGL.context -> string list -> shader_object
* modules for extensions
I like the idea of modules per OpenGL versions. We should have something similar for extensions, although there are some restrictions. Some extensions are part of 3.0 core module, or are not compatible with 3.0 and shouldn't be used there. So perhaps the OGL_2_1 module could have submodules with all allowed extensions (that we know of). Same for the other versions.
And you should be allowed to call functions from an extension only *after* you've checked that it is available. I was thinking of a dummy type, but maybe it is too inconvenient
module type Foo = sig
type present = private unit
val check : OGL.context -> present option
val func1 : present -> ...parameters
...
end
So maybe we should use first-class modules:
module OGL_2_1
...
val ext_Foo : OGL.context -> (module FooSig) option
val ext_Bar : OGL.context -> (module BarSig) option
And internally it'd be like:
let ext_Foo context =
if has_extension context "Foo" then
Some (module Foo_impl : FooSig)
Of course if someone wants to shoot themselves in the foot they can use Foo_impl directly in an unsafe way.
>
> Also notice that if I don't take webgl into considarations, it's only
> because I've not started to learn it yet, so I know nothing about it
> yet.
> If you have ideas about if and how it should be taken into account
> into a bindings for desktop programs, please feel free to talk about
> it.
>
>
> === REF SAMPLES ===
>
> [REF-1] :
>
> From the .ml part :
>
> type gl_enum = Gl_enum.t
>
> (* each gl-enums will be (eventually) different in each module version,
> in module GL21 `GL_QUADS, `GL_QUAD_STRIP and `GL_POLYGON
> will be there but not in GL32.
> And GL33 and GL41 will have additional ones:
> `GL_LINES_ADJACENCY
> `GL_LINE_STRIP_ADJACENCY
> `GL_TRIANGLES_ADJACENCY
> `GL_TRIANGLE_STRIP_ADJACENCY
> So each gl-enum would be generated accordingly for each version module *)
> type primitive = [
> | `GL_POINTS
> | `GL_LINES
> | `GL_LINE_LOOP
> | `GL_LINE_STRIP
> | `GL_TRIANGLES
> | `GL_TRIANGLE_STRIP
> | `GL_TRIANGLE_FAN
> | `GL_QUADS (** deprecated in core profile *)
> | `GL_QUAD_STRIP (** deprecated in core profile *)
> | `GL_POLYGON (** deprecated in core profile *)
> | `GL_LINES_ADJACENCY (** new in 3.3 and 4.1 *)
> | `GL_LINE_STRIP_ADJACENCY (** new in 3.3 and 4.1 *)
> | `GL_TRIANGLES_ADJACENCY (** new in 3.3 and 4.1 *)
> | `GL_TRIANGLE_STRIP_ADJACENCY (** new in 3.3 and 4.1 *)
> ]
>
> let int_of_primitive = function
> | `GL_POINTS -> Gl_enum.gl_points
> | `GL_LINES -> Gl_enum.gl_lines
> | `GL_LINE_LOOP -> Gl_enum.gl_line_loop
> | `GL_LINE_STRIP -> Gl_enum.gl_line_strip
> | `GL_TRIANGLES -> Gl_enum.gl_triangles
> | `GL_TRIANGLE_STRIP -> Gl_enum.gl_triangle_strip
> | `GL_TRIANGLE_FAN -> Gl_enum.gl_triangle_fan
> | `GL_QUADS -> Gl_enum.gl_quads
> | `GL_QUAD_STRIP -> Gl_enum.gl_quad_strip
> | `GL_POLYGON -> Gl_enum.gl_polygon
>
> external glBegin : gl_enum -> unit = "caml_glBegin" "noalloc"
> let glBegin prim =
> glBegin (int_of_primitive prim)
>
> let bit_of_clear_buffer = function
> | `GL_COLOR_BUFFER -> Gl_enum.gl_color_buffer_bit
> | `GL_DEPTH_BUFFER -> Gl_enum.gl_depth_buffer_bit
> | `GL_ACCUM_BUFFER -> Gl_enum.gl_accum_buffer_bit
> | `GL_STENCIL_BUFFER -> Gl_enum.gl_stencil_buffer_bit
>
> let enum_lor = Gl_enum.(lor)
>
> let int_of_clear_buffer lst =
> List.fold_left (fun mask v ->
> enum_lor mask (bit_of_clear_buffer v)
> ) Gl_enum.zero lst
>
> external glClear : gl_enum -> unit = "caml_glClear" "noalloc"
> let glClear buffers =
> glClear (int_of_clear_buffer buffers)
>
>
> From .mli part :
>
> val glBegin : primitive -> unit
> val glClear : clear_buffer list -> unit
>
> cat gl_funcs/stub_glBegin.c
>
> CAMLprim value
> caml_glBegin(value prim)
> {
> glBegin(Long_val(prim));
> return Val_unit;
> }
>
> cat gl_funcs/stub_glClear.c
>
> CAMLprim value
> caml_glClear(value mask)
> {
> glClear(Int_val(mask));
> return Val_unit;
> }
>
> From the Labeled interface :
> - module GlClear :
>
> type shape = [
> | `points
> | `lines
> | `line_loop
> | `line_strip
> | `triangles
> | `triangle_strip
> | `triangle_fan
> | `quads
> | `quad_strip
> | `polygon
> ]
>
> let int_of_shape = function
> | `points -> Gl_enum.gl_points
> | `lines -> Gl_enum.gl_lines
> | `line_loop -> Gl_enum.gl_line_loop
> | `line_strip -> Gl_enum.gl_line_strip
> | `triangles -> Gl_enum.gl_triangles
> | `triangle_strip -> Gl_enum.gl_triangle_strip
> | `triangle_fan -> Gl_enum.gl_triangle_fan
> | `quads -> Gl_enum.gl_quads
> | `quad_strip -> Gl_enum.gl_quad_strip
> | `polygon -> Gl_enum.gl_polygon
>
> external begins : gl_enum -> unit = "caml_glBegin" "noalloc"
> let begins shape =
> begins (int_of_shape shape)
>
> - module GlDraw :
>
> let bit_of_clear_buffer = function
> | `color -> Gl_enum.gl_color_buffer_bit
> | `depth -> Gl_enum.gl_depth_buffer_bit
> | `accum -> Gl_enum.gl_accum_buffer_bit
> | `stencil -> Gl_enum.gl_stencil_buffer_bit
>
> let int_of_clear_buffer lst =
> List.fold_left (fun mask v ->
> enum_lor mask (bit_of_clear_buffer v)
> ) Gl_enum.zero lst
>
> external clear : gl_enum -> unit = "caml_glClear" "noalloc"
> let clear buffers =
> clear (int_of_clear_buffer buffers)
>
>
> From file gl_enum.ml :
> (* this file not generated, because gl-enums values never change *)
> (* this file just reformated from C to ocaml code from the GL.h header
> with some interactive regexps in vim *)
>
> type t = int
>
> let ( lor ) = ( lor )
> let zero = 0
>
> (* Primitives *)
> let gl_points = 0x0000
> let gl_lines = 0x0001
> let gl_line_loop = 0x0002
> let gl_line_strip = 0x0003
> let gl_triangles = 0x0004
> let gl_triangle_strip = 0x0005
> let gl_triangle_fan = 0x0006
> let gl_quads = 0x0007
> let gl_quad_strip = 0x0008
> let gl_polygon = 0x0009
>
>
> From file gl_enum.mli :
>
> type t
>
> val ( lor ) : t -> t -> t
>
> val zero : t
>
> val gl_points : t
> val gl_lines : t
> val gl_line_loop : t
> val gl_line_strip : t
> val gl_triangles : t
> val gl_triangle_strip : t
> val gl_triangle_fan : t
> val gl_quads : t
> val gl_quad_strip : t
> val gl_polygon : t
>
>
> [REF-2] :
>
> glAccum 1.1 1.3 . 2.1 . . .
> glBegin 1.1 1.3 . 2.1 . . .
> glBindFramebuffer . . . . 3.3 4.1 es2
> glClipPlanef 1.1 1.3 es1 2.1 . . .
> GL_QUADS 1.1 1.3 es1 2.1 3.3 4.1 es2
> GL_TRIANGLE_STRIP_ADJACENCY . . . . 3.3 4.1 ?
>
More information about the OpenGL
mailing list