I am trying to learn OpenGL ES 2.0 to do some iPhone game development. I have read through multiple tutorials and some of the OpenGL ES 2.0 spec. All of the examples I have seen have created a single mesh, loaded it into a vertex buffer and then rendered it (with the expected translation, rotation, gradient, etc.)
My question is this: how do you render multiple objects in your scene that have different meshes and are moving independently? If I have a car and a motorcycle for example, can I create 2 vertex buffers and keep the mesh data for both around for each render call, and then just send in different matrices for the shader for each object? Or do I need to somehow translate the meshes and then combine them into a single mesh so that they can be rendered in one pass? I'm looking for more of the high-level strategy / program structure rather than code examples. I think I just have the wrong mental modal of how this works.
Thanks!
The best way I found to do this is using VAOs in addition to VBOs.
I'll first answer you question using VBOs only.
First of all, assume you have the two meshes of your two objects stored in the following arrays:
GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;
where:
GLfloat gCubeVertexData1[36] = {...};
GLfloat gCubeVertexData2[36] = {...};
And you also have to vertix buffers:
GLuint _vertexBufferCube1;
GLuint _vertexBufferCube2;
Now, to draw those two cubes (without VAOs), you have to do something like that:
in draw function (from OpenGLES template):
//Draw first object, bind VBO, adjust your attributes then call DrawArrays
glGenBuffers(1, &_vertexBufferCube1);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData1), gCubeVertexData1, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glDrawArrays(GL_TRIANGLES, 0, 36);
//Repeat for second object:
glGenBuffers(1, &_vertexBufferCube2);
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube2);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData2), gCubeVertexData2, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glUseProgram(_program);
glDrawArrays(GL_TRIANGLES, 0, 36);
This will answer you question. But now to use VAOs, your draw function code is much simpler (which is good because it is the repeated function):
First you will define to VAOs:
GLuint _vertexArray1;
GLuint _vertexArray2;
and then you will do all the steps previously done in draw method, you will do it in setupGL function but after binding to the VAO. Then in your draw function you just bind to the VAO you want.
VAO here is like a profile that contains a lot of properties (imagine a smart device profile). Instead of changing color, desktop, fonts.. etc every time you wish to change them, you do that once and save it under a profile name. Then you just switch the profile.
So you do that once, inside setupGL, then you switch between them in draw.
Of course you may say that you could have put the code (without VAO) in a function and call it. That's true, but VAOs are more efficient according to Apple:
http://developer.apple.com/library/ios/#documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/TechniquesforWorkingwithVertexData/TechniquesforWorkingwithVertexData.html#//apple_ref/doc/uid/TP40008793-CH107-SW1
Now to the code:
In setupGL:
glGenVertexArraysOES(1, &_vertexArray1); //Bind to first VAO
glBindVertexArrayOES(_vertexArray1);
glGenBuffers(1, &_vertexBufferCube1); //All steps from this one are done to first VAO only
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube1);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData1), gCubeVertexData1, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glGenVertexArraysOES(1, &_vertexArray2); // now bind to the second
glBindVertexArrayOES(_vertexArray2);
glGenBuffers(1, &_vertexBufferCube2); //repeat with the second mesh
glBindBuffer(GL_ARRAY_BUFFER, _vertexBufferCube2);
glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData2), gCubeVertexData2, GL_STATIC_DRAW);
glEnableVertexAttribArray(GLKVertexAttribPosition);
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(0));
glEnableVertexAttribArray(GLKVertexAttribNormal);
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24, BUFFER_OFFSET(12));
glBindVertexArrayOES(0);
Then finally in your draw method:
glBindVertexArrayOES(_vertexArray1);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArrayOES(_vertexArray2);
glDrawArrays(GL_TRIANGLES, 0, 36);
You maintain separate vertex/index buffers for different objects, yes. For example, you might have a RenderedObject class, and each instance would have it's own vertex buffer. One RenderedObject might take it's vertices from a house mesh, one might come from a character mesh, etc.
During rendering you set the appropriate transform/rotation/shading for the vertex buffer you're working with, perhaps something like:
void RenderedObject::render()
{
...
//set textures/shaders/transformations
glBindBuffer(GL_ARRAY_BUFFER, bufferID);
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount);
...
}
As mentioned in there other answer, the bufferID is just a GLuint not the entire contents of the buffer. If you need more details on creating vertex buffers and filling them with data, I'm happy to add those as well.
I realize this is an older post, but I was trying to find instructions on how to render multiple objects within OpenGL. I found a great tutorial, which describes how to render multiple objects and could be easily extended to render objects of different types (i.e. one cube, one pyramid).
The tutorial I'm posting also describes how to render objects using GLKit. I found it helpful and thought I'd repost it here. I hope it helps you too!
http://games.ianterrell.com/opengl-basics-with-glkit-in-ios5-encapsulated-drawing-and-animation/
If the meshes are different, you keep them in different vertex buffers. If they are similar (eg. animation, color) you pass arguments to the shader. You only have to keep the handles to the VBOs, not the vertex data itself if you don't plan on animating the object on the application side. Device side animation is possible.
I am hopefully-contributing to this older post, because I undertook to solve this problem a different way. Like the question asker, I have seen lots of "one-object" examples. I undertook to place all vertices into a single VBO, and then save the offset to that object's position (per object), rather than a buffer handle. It worked. The offset can be given as a parameter to glDrawElements as below. It seems obvious in retrospect, but I was not convinced until I saw it work. Please note that I have been working with "vertex pointer" rather than the more current "vertex attribute pointer". I am working towards the latter so I can leverage shaders.
All the objects "bind" to the same vertex buffer, prior to calling "draw elements".
gl.glVertexPointer( 3, GLES20.GL_FLOAT, 0, vertexBufferOffset );
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, indicesCount,
GLES20.GL_UNSIGNED_BYTE, indexBufferOffset
);
I did not find anywhere spelled out what was the purpose of this offset, so I took a chance. Also, this gotcha: you have to specify the offset in bytes, not vertices or floats. That is, multiply by four to get the correct position.
It is possible when using shaders, to use the same program for all objects without having to compile, link and create one for each. To do this, simply store the GLuint value to the program and then for each object "glUseProgram(programId);". As a result personal experience, i use a singleton to manage GLProgram structures.. (included below :))
#interface TDShaderSet : NSObject {
NSMutableDictionary *_attributes;
NSMutableDictionary *_uniforms;
GLuint _program;
}
#property (nonatomic, readonly, getter=getUniforms) NSMutableDictionary *uniforms;
#property (nonatomic, readonly, getter=getAttributes) NSMutableDictionary *attributes;
#property (nonatomic, readonly, getter=getProgram) GLuint program;
- (GLint) uniformLocation:(NSString*)name;
- (GLint) attribLocation:(NSString*)name;
#end
#interface TDProgamManager : NSObject
+ (TDProgamManager *) sharedInstance;
+ (TDProgamManager *) sharedInstanceWithContext:(EAGLContext*)context;
#property (nonatomic, readonly, getter=getAllPrograms) NSArray *allPrograms;
- (BOOL) loadShader:(NSString*)shaderName referenceName:(NSString*)refName;
- (TDShaderSet*) getProgramForRef:(NSString*)refName;
#end
#interface TDProgamManager () {
NSMutableDictionary *_glPrograms;
EAGLContext *_context;
}
#end
#implementation TDShaderSet
- (GLuint) getProgram
{
return _program;
}
- (NSMutableDictionary*) getUniforms
{
return _uniforms;
}
- (NSMutableDictionary*) getAttributes
{
return _attributes;
}
- (GLint) uniformLocation:(NSString*)name
{
NSNumber *number = [_uniforms objectForKey:name];
if (!number) {
GLint location = glGetUniformLocation(_program, name.UTF8String);
number = [NSNumber numberWithInt:location];
[_uniforms setObject:number forKey:name];
}
return number.intValue;
}
- (GLint) attribLocation:(NSString*)name
{
NSNumber *number = [_attributes objectForKey:name];
if (!number) {
GLint location = glGetAttribLocation(_program, name.UTF8String);
number = [NSNumber numberWithInt:location];
[_attributes setObject:number forKey:name];
}
return number.intValue;
}
- (id) initWithProgramId:(GLuint)program
{
self = [super init];
if (self) {
_attributes = [[NSMutableDictionary alloc] init];
_uniforms = [[NSMutableDictionary alloc] init];
_program = program;
}
return self;
}
#end
#implementation TDProgamManager {
#private
}
static TDProgamManager *_sharedSingleton = nil;
- (NSArray *) getAllPrograms
{
return _glPrograms.allValues;
}
- (TDShaderSet*) getProgramForRef:(NSString *)refName
{
return (TDShaderSet*)[_glPrograms objectForKey:refName];
}
- (BOOL) loadShader:(NSString*)shaderName referenceName:(NSString*)refName
{
NSAssert(_context, #"No Context available");
if ([_glPrograms objectForKey:refName]) return YES;
[EAGLContext setCurrentContext:_context];
GLuint vertShader, fragShader;
NSString *vertShaderPathname, *fragShaderPathname;
// Create shader program.
GLuint _program = glCreateProgram();
// Create and compile vertex shader.
vertShaderPathname = [[NSBundle mainBundle] pathForResource:shaderName ofType:#"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
NSLog(#"Failed to compile vertex shader");
return NO;
}
// Create and compile fragment shader.
fragShaderPathname = [[NSBundle mainBundle] pathForResource:shaderName ofType:#"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
NSLog(#"Failed to compile fragment shader");
return NO;
}
// Attach vertex shader to program.
glAttachShader(_program, vertShader);
// Attach fragment shader to program.
glAttachShader(_program, fragShader);
// Bind attribute locations.
// This needs to be done prior to linking.
glBindAttribLocation(_program, GLKVertexAttribPosition, "a_position");
glBindAttribLocation(_program, GLKVertexAttribNormal, "a_normal");
glBindAttribLocation(_program, GLKVertexAttribTexCoord0, "a_texCoord");
// Link program.
if (![self linkProgram:_program]) {
NSLog(#"Failed to link program: %d", _program);
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (_program) {
glDeleteProgram(_program);
_program = 0;
}
return NO;
}
// Release vertex and fragment shaders.
if (vertShader) {
glDetachShader(_program, vertShader);
glDeleteShader(vertShader);
}
if (fragShader) {
glDetachShader(_program, fragShader);
glDeleteShader(fragShader);
}
TDShaderSet *_newSet = [[TDShaderSet alloc] initWithProgramId:_program];
[_glPrograms setValue:_newSet forKey:refName];
return YES;
}
- (BOOL) compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
GLint status;
const GLchar *source;
source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
if (!source) {
NSLog(#"Failed to load vertex shader");
return NO;
}
*shader = glCreateShader(type);
glShaderSource(*shader, 1, &source, NULL);
glCompileShader(*shader);
#if defined(DEBUG)
GLint logLength;
glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetShaderInfoLog(*shader, logLength, &logLength, log);
NSLog(#"Shader compile log:\n%s", log);
free(log);
}
#endif
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
glDeleteShader(*shader);
return NO;
}
return YES;
}
- (BOOL) linkProgram:(GLuint)prog
{
GLint status;
glLinkProgram(prog);
#if defined(DEBUG)
GLint logLength;
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(#"Program link log:\n%s", log);
free(log);
}
#endif
glGetProgramiv(prog, GL_LINK_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
- (BOOL) validateProgram:(GLuint)prog
{
GLint logLength, status;
glValidateProgram(prog);
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
if (logLength > 0) {
GLchar *log = (GLchar *)malloc(logLength);
glGetProgramInfoLog(prog, logLength, &logLength, log);
NSLog(#"Program validate log:\n%s", log);
free(log);
}
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
if (status == 0) {
return NO;
}
return YES;
}
#pragma mark - Singleton stuff... Don't mess with this other than proxyInit!
- (void) proxyInit
{
_glPrograms = [[NSMutableDictionary alloc] init];
}
- (id) init
{
Class myClass = [self class];
#synchronized(myClass) {
if (!_sharedSingleton) {
if (self = [super init]) {
_sharedSingleton = self;
[self proxyInit];
}
}
}
return _sharedSingleton;
}
+ (TDProgamManager *) sharedInstance
{
#synchronized(self) {
if (!_sharedSingleton) {
_sharedSingleton = [[self alloc] init];
}
}
return _sharedSingleton;
}
+ (TDProgamManager *) sharedInstanceWithContext:(EAGLContext*)context
{
#synchronized(self) {
if (!_sharedSingleton) {
_sharedSingleton = [[self alloc] init];
}
_sharedSingleton->_context = context;
}
return _sharedSingleton;
}
+ (id) allocWithZone:(NSZone *)zone
{
#synchronized(self) {
if (!_sharedSingleton) {
return [super allocWithZone:zone];
}
}
return _sharedSingleton;
}
+ (id) copyWithZone:(NSZone *)zone
{
return self;
}
#end
Note that once data-spaces (attributes/uniforms) are passed in, you DONT have to pass them in each render cycle but only when invalidated. This results a serious GPU performance gain.
Per the VBO side of things, the answer above spells out how best to deal with this. Per the orientation side of the equation, you'll need a mechanism to nest tdobjects inside each other (similar to UIView and children under iOS) and then evaluation relative rotations to parents etc.
Good luck !
Related
I am trying to implement a container view with Auto Layout for OS X that operates similarly to NSStackView, but with a few differences that NSStackView does not handle (and I need 10.7 support anyway). My rules are:
The subviews are arranged either horizontally or vertically.
In the primary direction, the subviews by default take their intrinsic size.
If a subview is marked "stretchy", it will take whatever space remains after laying out all non-stretchy subviews. Multiple stretchy subviews get equal distributions of the remaining space.
If no subviews are stretchy, the container can grow in the primary direction, showing empty space after the last subview. Nesting two such stacks with the same orientation prefers the outer one.
Along the secondary direction, subviews clamp to the edges of the container view and grow freely.
I thought this could be done in a simple way, connecting the views in the primary direction one after another and then in the secondary direction using the |[view]| visual format. Lack of a stretchy view is handled with a NSView with intrinsic content size 0x0 as the last view.
This mostly worked. Unfortunately, ambiguity arose with a nested tree of horizontally oriented stacks of the form (represented using HTML for expository purposes)
.box {
display: inline-block;
border: 1px solid black;
padding: 0.5em 0.5em 0.5em 0.5em;
}
.outermost-box {
display: inline-block;
border: 1px solid black;
padding: 0.5em 0.5em 0.5em 0.5em;
width: 100%;
}
<div class="outermost-box">
<div class="box">
<input type="button" value="These">
<input type="button" value="Buttons" disabled>
</div>
<div class="box">
<input type="button" value="are">
<div class="box">
<input type="button" value="in" disabled>
</div>
</div>
<div class="box">
<div class="box">
<input type="button" value="nested">
<div class="box">
<input type="button" value="boxes" disabled>
</div>
</div>
</div>
</div>
where all the stacks have no stretchy subviews. Under my definition, the outermost box stretches, and only that one stretches. However, Auto Layout will randomly assign the extra space to one of the inner boxes:
Changing the extra view to a NSLayoutRelationLessThanOrEqual relation (last view.trailing <= superview.trailing) also did not help. I'm going to keep this model for the rest of this post, though, as my next attempts are based on it.
Then I decided to try having the container ask its superview if it should expand. This fixed the above problem, but introduced another problem with deep chains of containers that alternate between horizontal and vertical:
The buttons labelled "Right Margin Test" should stretch, but they either aren't stretching or are stretching but also clipping the views on the sides (I don't have a screenshot of this right now; sorry).
Then I decided to have both the <= and alternate == constraints on the right edge on at the same time, setting == to a low priority if there should be extra space. This new one mostly works, but now has a weird problem. If I resize the window large enough on the Page 3 shown above, then switch to Page 4, I get
and then if I resize I get
even though Page 4 should have space at the bottom under all conditions. Sometimes the bottom of the button can be seen, and visualizing its vertical constraints show it thinks it wants to be as tall as the matrix of radio buttons (right now a NSMatrix; changing it to a bunch of NSButtons will wait until after I fix all these Auto Layout issues).
I'm really not sure what's going on or how to fix any of this. I tried making the == constraint I mentioned have a settable "real hugging priority" of its own, but that just made things break in more spectacular ways.'
There's also problems with positions of tab views being too low initially and taking a few layout cycles to set properly...
Everything shown is done with these containers, NSBoxes with one subview, and NSTabs with one subview. I'll paste the code for my container as it stands now below.
So what about Auto Layout do I not understand that I can't just make it work right with the obvious code? Or can NSStackView do all of what I want and I should just use it instead? (That assumes alignment takes Width and Height as valid, which Interface Builder does not seem to say it does).
Thanks!
// 15 august 2015
#import "uipriv_darwin.h"
// TODOs:
// - tab on page 2 is glitched initially and doesn't grow
// - page 3 doesn't work right; probably due to our shouldExpand logic being applied incorrectly
// TODOs to confirm
// - 10.8: if we switch to page 4, then switch back to page 1, check Spaced, and go back to page 4, some controls (progress bar, popup button) are clipped on the sides
#interface boxChild : NSObject
#property uiControl *c;
#property BOOL stretchy;
#property NSLayoutPriority oldHorzHuggingPri;
#property NSLayoutPriority oldVertHuggingPri;
- (NSView *)view;
#end
#interface boxView : NSView {
uiBox *b;
NSMutableArray *children;
BOOL vertical;
int padded;
NSLayoutConstraint *first;
NSMutableArray *inBetweens;
NSLayoutConstraint *last, *last2;
NSMutableArray *otherConstraints;
NSLayoutAttribute primaryStart;
NSLayoutAttribute primaryEnd;
NSLayoutAttribute secondaryStart;
NSLayoutAttribute secondaryEnd;
NSLayoutAttribute primarySize;
NSLayoutConstraintOrientation primaryOrientation;
NSLayoutConstraintOrientation secondaryOrientation;
}
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
- (void)onDestroy;
- (void)removeOurConstraints;
- (void)forAll:(void (^)(uintmax_t i, boxChild *b))closure;
- (boxChild *)child:(uintmax_t)i;
- (BOOL)isVertical;
- (void)append:(uiControl *)c stretchy:(int)stretchy;
- (void)delete:(uintmax_t)n;
- (int)isPadded;
- (void)setPadded:(int)p;
#end
struct uiBox {
uiDarwinControl c;
boxView *view;
};
#implementation boxChild
- (NSView *)view
{
return (NSView *) uiControlHandle(self.c);
}
#end
#implementation boxView
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
{
self = [super initWithFrame:NSZeroRect];
if (self != nil) {
// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
self->b = bb;
self->vertical = vert;
self->children = [NSMutableArray new];
self->inBetweens = [NSMutableArray new];
self->otherConstraints = [NSMutableArray new];
if (self->vertical) {
self->primaryStart = NSLayoutAttributeTop;
self->primaryEnd = NSLayoutAttributeBottom;
self->secondaryStart = NSLayoutAttributeLeading;
self->secondaryEnd = NSLayoutAttributeTrailing;
self->primarySize = NSLayoutAttributeHeight;
self->primaryOrientation = NSLayoutConstraintOrientationVertical;
self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
} else {
self->primaryStart = NSLayoutAttributeLeading;
self->primaryEnd = NSLayoutAttributeTrailing;
self->secondaryStart = NSLayoutAttributeTop;
self->secondaryEnd = NSLayoutAttributeBottom;
self->primarySize = NSLayoutAttributeWidth;
self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
}
}
return self;
}
- (void)onDestroy
{
boxChild *bc;
uintmax_t i, n;
[self removeOurConstraints];
[self->first release];
[self->inBetweens release];
[self->last release];
[self->last2 release];
[self->otherConstraints release];
n = [self->children count];
for (i = 0; i < n; i++) {
bc = [self child:i];
uiControlSetParent(bc.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
uiControlDestroy(bc.c);
}
[self->children release];
}
- (void)removeOurConstraints
{
[self removeConstraint:self->first];
[self removeConstraints:self->inBetweens];
[self removeConstraint:self->last];
[self removeConstraint:self->last2];
[self removeConstraints:self->otherConstraints];
}
- (void)forAll:(void (^)(uintmax_t i, boxChild *b))closure
{
uintmax_t i, n;
n = [self->children count];
for (i = 0; i < n; i++)
closure(i, [self child:i]);
}
- (boxChild *)child:(uintmax_t)i
{
return (boxChild *) [self->children objectAtIndex:i];
}
- (BOOL)isVertical
{
return self->vertical;
}
// TODO something about spinbox hugging
- (void)updateConstraints
{
uintmax_t i, n;
BOOL hasStretchy;
NSView *firstStretchy = nil;
CGFloat padding;
NSView *prev, *next;
NSLayoutConstraint *c;
NSLayoutPriority priority;
[super updateConstraints];
[self removeOurConstraints];
n = [self->children count];
if (n == 0)
return;
padding = 0;
if (self->padded)
padding = 8.0; // TODO named constant
// first, attach the first view to the leading
prev = [[self child:0] view];
self->first = mkConstraint(prev, self->primaryStart,
NSLayoutRelationEqual,
self, self->primaryStart,
1, 0,
#"uiBox first primary constraint");
[self addConstraint:self->first];
[self->first retain];
// next, assemble the views in the primary direction
// they all go in a straight line
// also figure out whether we have stretchy controls, and which is the first
if ([self child:0].stretchy) {
hasStretchy = YES;
firstStretchy = prev;
} else
hasStretchy = NO;
for (i = 1; i < n; i++) {
next = [[self child:i] view];
if (!hasStretchy && [self child:i].stretchy) {
hasStretchy = YES;
firstStretchy = next;
}
c = mkConstraint(next, self->primaryStart,
NSLayoutRelationEqual,
prev, self->primaryEnd,
1, padding,
#"uiBox later primary constraint");
[self addConstraint:c];
[self->inBetweens addObject:c];
prev = next;
}
// and finally end the primary direction
self->last = mkConstraint(prev, self->primaryEnd,
NSLayoutRelationLessThanOrEqual,
self, self->primaryEnd,
1, 0,
#"uiBox last primary constraint");
[self addConstraint:self->last];
[self->last retain];
// if there is a stretchy control, add the no-stretchy view
self->last2 = mkConstraint(prev, self->primaryEnd,
NSLayoutRelationEqual,
self, self->primaryEnd,
1, 0,
#"uiBox last2 primary constraint");
priority = NSLayoutPriorityRequired;
if (!hasStretchy) {
BOOL shouldExpand = NO;
uiControl *parent;
parent = uiControlParent(uiControl(self->b));
if (parent != nil)
if (self->vertical)
shouldExpand = uiDarwinControlChildrenShouldAllowSpaceAtBottom(uiDarwinControl(parent));
else
shouldExpand = uiDarwinControlChildrenShouldAllowSpaceAtTrailingEdge(uiDarwinControl(parent));
if (shouldExpand)
priority = NSLayoutPriorityDefaultLow;
}
[self->last2 setPriority:priority];
[self addConstraint:self->last2];
[self->last2 retain];
// next: assemble the views in the secondary direction
// each of them will span the secondary direction
for (i = 0; i < n; i++) {
prev = [[self child:i] view];
c = mkConstraint(prev, self->secondaryStart,
NSLayoutRelationEqual,
self, self->secondaryStart,
1, 0,
#"uiBox start secondary constraint");
[self addConstraint:c];
[self->otherConstraints addObject:c];
c = mkConstraint(prev, self->secondaryEnd,
NSLayoutRelationEqual,
self, self->secondaryEnd,
1, 0,
#"uiBox end secondary constraint");
[self addConstraint:c];
[self->otherConstraints addObject:c];
}
// finally, set sizes for stretchy controls
if (hasStretchy)
for (i = 0; i < n; i++) {
if (![self child:i].stretchy)
continue;
prev = [[self child:i] view];
if (prev == firstStretchy)
continue;
c = mkConstraint(prev, self->primarySize,
NSLayoutRelationEqual,
firstStretchy, self->primarySize,
1, 0,
#"uiBox stretchy sizing");
[self addConstraint:c];
[self->otherConstraints addObject:c];
}
}
- (void)append:(uiControl *)c stretchy:(int)stretchy
{
boxChild *bc;
NSView *childView;
bc = [boxChild new];
bc.c = c;
bc.stretchy = stretchy;
childView = [bc view];
bc.oldHorzHuggingPri = horzHuggingPri(childView);
bc.oldVertHuggingPri = vertHuggingPri(childView);
uiControlSetParent(bc.c, uiControl(self->b));
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));
// if a control is stretchy, it should not hug in the primary direction
// otherwise, it should *forcibly* hug
if (stretchy)
setHuggingPri(childView, NSLayoutPriorityDefaultLow, self->primaryOrientation);
else
// TODO will default high work?
setHuggingPri(childView, NSLayoutPriorityRequired, self->primaryOrientation);
// make sure controls don't hug their secondary direction so they fill the width of the view
setHuggingPri(childView, NSLayoutPriorityDefaultLow, self->secondaryOrientation);
[self->children addObject:bc];
[bc release]; // we don't need the initial reference now
[self setNeedsUpdateConstraints:YES];
}
- (void)delete:(uintmax_t)n
{
boxChild *bc;
NSView *removedView;
bc = [self child:n];
removedView = [bc view];
uiControlSetParent(bc.c, NULL);
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
setHorzHuggingPri(removedView, bc.oldHorzHuggingPri);
setVertHuggingPri(removedView, bc.oldVertHuggingPri);
[self->children removeObjectAtIndex:n];
[self setNeedsUpdateConstraints:YES];
}
- (int)isPadded
{
return self->padded;
}
- (void)setPadded:(int)p
{
CGFloat padding;
uintmax_t i, n;
NSLayoutConstraint *c;
self->padded = p;
// TODO split into method (using above code)
padding = 0;
if (self->padded)
padding = 8.0;
n = [self->inBetweens count];
for (i = 0; i < n; i++) {
c = (NSLayoutConstraint *) [self->inBetweens objectAtIndex:i];
[c setConstant:padding];
}
// TODO call anything?
}
#end
static void uiBoxDestroy(uiControl *c)
{
uiBox *b = uiBox(c);
[b->view onDestroy];
[b->view release];
uiFreeControl(uiControl(b));
}
uiDarwinControlDefaultHandle(uiBox, view)
uiDarwinControlDefaultParent(uiBox, view)
uiDarwinControlDefaultSetParent(uiBox, view)
uiDarwinControlDefaultToplevel(uiBox, view)
uiDarwinControlDefaultVisible(uiBox, view)
uiDarwinControlDefaultShow(uiBox, view)
uiDarwinControlDefaultHide(uiBox, view)
uiDarwinControlDefaultEnabled(uiBox, view)
uiDarwinControlDefaultEnable(uiBox, view)
uiDarwinControlDefaultDisable(uiBox, view)
static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
{
uiBox *b = uiBox(c);
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
return;
[b->view forAll:^(uintmax_t i, boxChild *bc) {
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
}];
}
uiDarwinControlDefaultSetSuperview(uiBox, view)
static BOOL uiBoxChildrenShouldAllowSpaceAtTrailingEdge(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
// return NO if this box is horizontal so nested horizontal boxes don't lead to ambiguity
return [b->view isVertical];
}
static BOOL uiBoxChildrenShouldAllowSpaceAtBottom(uiDarwinControl *c)
{
uiBox *b = uiBox(c);
// return NO if this box is vertical so nested vertical boxes don't lead to ambiguity
return ![b->view isVertical];
}
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
{
[b->view append:c stretchy:stretchy];
}
void uiBoxDelete(uiBox *b, uintmax_t n)
{
[b->view delete:n];
}
int uiBoxPadded(uiBox *b)
{
return [b->view isPadded];
}
void uiBoxSetPadded(uiBox *b, int padded)
{
[b->view setPadded:padded];
}
static uiBox *finishNewBox(BOOL vertical)
{
uiBox *b;
uiDarwinNewControl(uiBox, b);
b->view = [[boxView alloc] initWithVertical:vertical b:b];
return b;
}
uiBox *uiNewHorizontalBox(void)
{
return finishNewBox(NO);
}
uiBox *uiNewVerticalBox(void)
{
return finishNewBox(YES);
}
Solved this myself.
The primary change is that instead of having the has-extra-space-after-it is not done by the constraints on self, but on its superview. The superview asks self if it should take the extra space, and if so, it allocates the extra space (using a >= constraint to the superview edge instead of a == constraint).
A variety of other minor fixes fixes the edge case. In particular, everywhere where I do
relation = NSLayoutRelationSomething;
if (condition)
relation = NSLayoutRelationSomethingElse;
constraint = [NSLayoutConstraint constraintWithArg:arg arg:arg
relation:relation
...]
I change to using two constraints, setting the priority of them both based on the condition. This should probably be an Auto Layout best practice, since it works so well...
Thanks anyway!
I'm beginner in Open GL but I already can draw simple triangle, rectangle etc.
My problem is:
I have the structure and static array of that structure
typedef struct {
GLKVector3 Position;
} Vertex;
const Vertex Vertices[] = {
{{0.0, 0.0, 0.0}},
{{0.5, 0.0, 0.0}},
{{0.5, 0.5, 0.0}},
{{0.0, 0.5, 0.0}},
{{0.0, 0.0, 0.0}}
};
...some code
but I need array of vertices create dynamically... :(
Example:
typedef struct {
GLKVector3 Position;
} Vertex;
instance variable - iVertices of type Vertex
- (void) viewDidLoad {
int numOfVertices = 0;
Vertex vertices[] = {{0.0, 0.0, 0.0}};
[self addVertex:vertices atIndex:numOfVertices];
numOfVertices ++;
Vertex vertices[] = {{0.5, 0.0, 0.0}};
[self addVertex:vertices atIndex:numOfVertices];
numOfVertices ++;
Vertex vertices[] = {{0.5, 0.5, 0.0}};
[self addVertex:vertices atIndex:numOfVertices];
}
- (void) addVertex:(Vertex) vertex atIndex:(int) num {
iVertices[num] = vertex;
}
...and somewhere
glBufferData(GL_ARRAY_BUFFER,
sizeof(iVertices),
iVertices,
GL_STATIC_DRAW);
and this is not allowed in Objective-C or I don't know how to do it :(
malloc nor callow doesn't help to me...
Thanks a lot!
Your main problem here is that you can't just take the sizeof an array that is an instance variable because it's a pointer which will return a size of 8. Instead you're going to have to save the count of the array somewhere else as another instance variable (or use numOfVertices) and multiply it by the sizeof(int). So something like glBufferData(GL_ARRAY_BUFFER, numOfVariable*sizeof(int), iVertices, GL_STATIC_DRAW); should work for your case.
First of all, I'm an Objective-C novice. So I'm not very familiar with OS X or iOS development. My experience is mostly in Java.
I'm creating an agent-based modeling-framework. I'd like to display the simulations and to do that I'm writing a little application. First, a little bit about the framework. The framework has a World class, in which there is a start method, which iterates over all agents and has them perform their tasks. At the end of one "step" of the world (i.e., after all the agents have done their thing), the start method calls the intercept method of an object that implements InterceptorProtocol. This object was previously passed in via the constructor. Using the interceptor, anyone can get a hook into the state of the world. This is useful for logging, or in the scenario that I'm trying to accomplish: displaying the information in a graphical manner. The call to intercept is synchronous.
Now as far as the GUI app is concerned, it is pretty simple. I have a controller that initializes a custom view. This custom view also implements InterceptorProtocol so that it can listen in, to what happens in the world. I create a World object and pass in the view as an interceptor. The view maintains a reference to the world through a private property and so once I have initialized the world, I set the view's world property to the world I have just created (I realize that this creates a cycle, but I need a reference to the world in the drawRect method of the view and the only way I can have it is if I maintain a reference to it from the class).
Since the world's start method is synchronous, I don't start the world up immediately. In the drawRect method I check to see if the world is running. If it is not, I start it up in a background thread. If it is, I examine the world and display all the graphics that I need to.
In the intercept method (which gets called from start running on the background thread), I set setNeedsToDisplay to YES. Since the start method of the world is running in a separate thread, I also have a lock object that I use to synchronize so that I'm not working on the World object while it's being mutated (this part is kind of janky and it's probably not working the way I expect it to - there are more than a few rough spots and I'm simply trying to get a little bit working; I plan to clean up later).
My problem is that the view renders some stuff, and then it pretty much locks up. I can see that the NSLog statements are being called and so the code is running, but nothing is getting updated on the view.
Here's some of the pertinent code:
MasterViewController
#import "MasterViewController.h"
#import "World.h"
#import "InfectableBug.h"
#interface MasterViewController ()
#end
#implementation MasterViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_worldView = [[WorldView alloc] init];
World* world = [[World alloc] initWithName: #"Bhumi"
rows: 100
columns: 100
iterations: 2000
snapshotInterval: 1
interceptor: _worldView];
for(int i = 0; i < 999; i++) {
NSMutableString* name = [NSMutableString stringWithString: #"HealthyBug"];
[name appendString: [[NSNumber numberWithInt: i] stringValue]];
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: name
layer: #"FirstLayer"
infected: NO
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
}
NSLog(#"Added all bugs. Going to add infected");
[world addBug: [[InfectableBug alloc] initWithWorld: world
name: #"InfectedBug"
layer: #"FirstLayer"
infected: YES
infectionRadius: 1
incubationPeriod: 10
infectionStartIteration: 0]];
[_worldView setWorld: world];
//[world start];
}
return self;
}
- (NSView*) view {
return self.worldView;
}
#end
WorldView
#import "WorldView.h"
#import "World.h"
#import "InfectableBug.h"
#implementation WorldView
#synthesize world;
- (id) initWithFrame:(NSRect) frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void) drawRect:(NSRect) dirtyRect {
CGContextRef myContext = [[NSGraphicsContext currentContext] graphicsPort];
CGContextClearRect(myContext, CGRectMake(0, 0, 1024, 768));
NSUInteger rows = [world rows];
NSUInteger columns = [world columns];
NSUInteger cellWidth = 1024 / columns;
NSUInteger cellHeight = 768 / rows;
if([world running]) {
#synchronized (_lock) {
//Ideally we would need layers, but for now let's just get this to display
NSArray* bugs = [world bugs];
NSEnumerator* enumerator = [bugs objectEnumerator];
InfectableBug* bug;
while ((bug = [enumerator nextObject])) {
if([bug infected] == YES) {
CGContextSetRGBFillColor(myContext, 128, 0, 0, 1);
} else {
CGContextSetRGBFillColor(myContext, 0, 0, 128, 1);
}
NSLog(#"Drawing bug %# at %lu, %lu with width %lu and height %lu", [bug name], [bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight);
CGContextFillRect(myContext, CGRectMake([bug x] * cellWidth, [bug y] * cellHeight, cellWidth, cellHeight));
}
}
} else {
[world performSelectorInBackground: #selector(start) withObject: nil];
}
}
- (BOOL) isFlipped {
return YES;
}
- (void) intercept: (World *) aWorld {
struct timespec time;
time.tv_sec = 0;
time.tv_nsec = 500000000L;
//nanosleep(&time, NULL);
#synchronized (_lock) {
[self setNeedsDisplay: YES];
}
}
#end
start method in World.m:
- (void) start {
running = YES;
while(currentIteration < iterations) {
#autoreleasepool {
[bugs shuffle];
NSEnumerator* bugEnumerator = [bugs objectEnumerator];
Bug* bug;
while((bug = [bugEnumerator nextObject])) {
NSString* originalLayer = [bug layer];
NSUInteger originalX = [bug x];
NSUInteger originalY = [bug y];
//NSLog(#"Bug %# is going to act and location %i:%i is %#", [bug name], [bug x], [bug y], [self isOccupied: [bug layer] x: [bug x] y: [bug y]] ? #"occupied" : #"not occupied");
[bug act];
//NSLog(#"Bug has acted");
if(![originalLayer isEqualToString: [bug layer]] || originalX != [bug x] || originalY != [bug y]) {
//NSLog(#"Bug has moved");
[self moveBugFrom: originalLayer atX: originalX atY: originalY toLayer: [bug layer] atX: [bug x] atY: [bug y]];
//NSLog(#"Updated bug position");
}
}
if(currentIteration % snapshotInterval == 0) {
[interceptor intercept: self];
}
currentIteration++;
}
}
//NSLog(#"Done.");
}
Please let me know if you'd like to see any other code. I realize that the code is not pretty; I was just trying to get stuff to work and I plan on cleaning it up later. Also, if I'm violating an Objective-C best practices, please let me know!
Stepping out for a bit; sorry if I don't respond immediately!
Whew, quiet a question for probably a simple answer: ;)
UI updates have to be performed on the main thread
If I read your code correctly, you call the start method on a background thread. The start method contains stuff like moveBugFrom:... and also the intercept: method. The intercept method thus calls setNeedsDisplay: on a background thread.
Have all UI related stuff perform on the main thread. Your best bet is to use Grand Central Dispatch, unless you need to support iOS < 4 or OS X < 10.6 (or was it 10.7?), like this:
dispatch_async(dispatch_get_main_queue(), ^{
// perform UI updates
});
I've been looking at the new OpenGL framework for iOS, aptly named GLKit, and have been playing around with porting some existing OpenGL 1.0 code to OpenGL ES 2.0 just to dip my toe in the water and get to grips with things.
After reading the API and a whole ream of other best practices provided by Apple and the OpenGL documentation, i've had it pretty much ingrained into me that I should be using Vertex Buffer Objects and using "elements" or rather, vertex indices. There seems to be a lot of mention of optimising memory storage by using padding where necessary too but that's a conversation for another day perhaps ;)
I read on SO a while ago about the benefits of using NSMutableData over classic malloc/free and wanted to try and take this approach when writing my VBO. So far i've managed to bungle together a snippet that looks like i'm heading down the right track but i'm not entirely sure about how much data a VBO should contain. Here's what i've got so far:
//import headers
#import <GLKit/GLKit.h>
#pragma mark -
#pragma mark InterleavingVertexData
//vertex buffer object struct
struct InterleavingVertexData
{
//vertices
GLKVector3 vertices;
//normals
GLKVector3 normal;
//color
GLKVector4 color;
//texture coordinates
GLKVector2 texture;
};
typedef struct InterleavingVertexData InterleavingVertexData;
#pragma mark -
#pragma mark VertexIndices
//vertex indices struct
struct VertexIndices
{
//vertex indices
GLuint a;
GLuint b;
GLuint c;
};
typedef struct VertexIndices VertexIndices;
//create and return a vertex index with specified indices
static inline VertexIndices VertexIndicesMake(GLuint a, GLuint b, GLuint c)
{
//declare vertex indices
VertexIndices vertexIndices;
//set indices
vertexIndices.a = a;
vertexIndices.b = b;
vertexIndices.c = c;
//return vertex indices
return vertexIndices;
}
#pragma mark -
#pragma mark VertexBuffer
//vertex buffer struct
struct VertexBuffer
{
//vertex data
NSMutableData *vertexData;
//vertex indices
NSMutableData *indices;
//total number of vertices
NSUInteger totalVertices;
//total number of indices
NSUInteger totalIndices;
};
typedef struct VertexBuffer VertexBuffer;
//create and return a vertex buffer with allocated data
static inline VertexBuffer VertexBufferMake(NSUInteger totalVertices, NSUInteger totalIndices)
{
//declare vertex buffer
VertexBuffer vertexBuffer;
//set vertices and indices count
vertexBuffer.totalVertices = totalVertices;
vertexBuffer.totalIndices = totalIndices;
//set vertex data and indices
vertexBuffer.vertexData = nil;
vertexBuffer.indices = nil;
//check vertices count
if(totalVertices > 0)
{
//allocate data
vertexBuffer.vertexData = [[NSMutableData alloc] initWithLength:(sizeof(InterleavingVertexData) * totalVertices)];
}
//check indices count
if(totalIndices > 0)
{
//allocate data
vertexBuffer.indices = [[NSMutableData alloc] initWithLength:(sizeof(VertexIndices) * totalIndices)];
}
//return vertex buffer
return vertexBuffer;
}
//grow or shrink a vertex buffer
static inline void VertexBufferResize(VertexBuffer *vertexBuffer, NSUInteger totalVertices, NSUInteger totalIndices)
{
//check adjusted vertices count
if(vertexBuffer->totalVertices != totalVertices && totalVertices > 0)
{
//set vertices count
vertexBuffer->totalVertices = totalVertices;
//check data is valid
if(vertexBuffer->vertexData)
{
//allocate data
[vertexBuffer->vertexData setLength:(sizeof(InterleavingVertexData) * totalVertices)];
}
else
{
//allocate data
vertexBuffer->vertexData = [[NSMutableData alloc] initWithLength:(sizeof(InterleavingVertexData) * totalVertices)];
}
}
//check adjusted indices count
if(vertexBuffer->totalIndices != totalIndices && totalIndices > 0)
{
//set indices count
vertexBuffer->totalIndices = totalIndices;
//check data is valid
if(vertexBuffer->indices)
{
//allocate data
[vertexBuffer->indices setLength:(sizeof(VertexIndices) * totalIndices)];
}
else
{
//allocate data
vertexBuffer->indices = [[NSMutableData alloc] initWithLength:(sizeof(VertexIndices) * totalIndices)];
}
}
}
//release vertex buffer data
static inline void VertexBufferRelease(VertexBuffer *vertexBuffer)
{
//set vertices and indices count
vertexBuffer->totalVertices = 0;
vertexBuffer->totalIndices = 0;
//check vertices are valid
if(vertexBuffer->vertexData)
{
//clean up
[vertexBuffer->vertexData release];
vertexBuffer->vertexData = nil;
}
//check indices are valid
if(vertexBuffer->indices)
{
//clean up
[vertexBuffer->indices release];
vertexBuffer->indices = nil;
}
}
Currently, the interleaving vertex data contains enough to store the vertices, normals, colors and texture coordinates for each vertex. I was under the impression that there would be an equal number of vertices and indices but in practice this obviously isn't the case so for this reason, the indices are part of the VBO rather than the InterleavingVertexData.
Question Updated:
I've updated the code above after managing to wrangle it into a working state. Hopefully it will come in useful to someone in the future.
Now that i've managed to set everything up, i'm having trouble getting the expected results from rendering the content bound to the VBO. Here's the code i've got so far for loading my data into OpenGL:
//generate buffers
glGenBuffers(2, buffers);
//bind vertices buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glBufferData(GL_ARRAY_BUFFER, (sizeof(InterleavingVertexData) * vertexBuffer.totalVertices), self.vertexData, GL_STATIC_DRAW);
//bind indices buffer
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (sizeof(VertexIndices) * vertexBuffer.totalIndices), self.vertexIndices, GL_STATIC_DRAW);
//reset buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
And the code for rendering everything:
//enable required attributes
glEnableVertexAttribArray(GLKVertexAttribPosition);
glEnableVertexAttribArray(GLKVertexAttribNormal);
glEnableVertexAttribArray(GLKVertexAttribColor);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
//bind buffers
glBindBuffer(GL_ARRAY_BUFFER, buffers[0]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]);
//set shape attributes
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, vertices));
glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_TRUE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, normal));
glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_TRUE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, color));
glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_TRUE, sizeof(InterleavingVertexData), (void *)offsetof(InterleavingVertexData, texture));
//draw shape
glDrawElements(GL_TRIANGLES, vertexBuffer.totalIndices, GL_UNSIGNED_INT, (void *)0);
//reset buffers
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//disable atttributes
glDisableVertexAttribArray(GLKVertexAttribTexCoord0);
glDisableVertexAttribArray(GLKVertexAttribColor);
glDisableVertexAttribArray(GLKVertexAttribNormal);
glDisableVertexAttribArray(GLKVertexAttribPosition);
Whilst my iPhone hasn't yet exploded with awesome graphics of unicorns shooting rainbows from their eyes, I haven't been able to render a simple shape in it's entirety without tearing my hair out.
From the rendering it looks as though only 1/3rd of each shape is being drawn, perhaps 1/2 depending on the viewing angle. It seems the culprit it the count parameter passed to glDrawElements as fiddling with this has differing results but I've read the documentation and checked the value over and over again and it does indeed expect the total number of indices (which is what i'm passing currently).
As I mentioned in my original question, i'm quite confused by VBO's currently or rather, confused by the implementation rather than the concept at least. If anyone would be so kind as to cast an eye over my implementation, that would be super awesome as i'm sure i've made a rookie error somewhere along the way but you know how it is when you stare at something for hours on end with no progress.
Thanks for reading!
I think I see your problem.
You've got a struct, VertexIndices which contains three indices, or the indices for one triangle. When you bind your IBO (Index Buffer Object, the buffer object containing your indices), you do this:
glBufferData(GL_ELEMENT_ARRAY_BUFFER, (sizeof(VertexIndices) * vertexBuffer.totalIndices), self.vertexIndices, GL_STATIC_DRAW);
Which is fine. The size parameter in glBufferData is in bytes so you're multiplying sizeof(3 floats) by the number of groups of 3 floats that you have. Great.
But then when you actually call glDrawElements, you do this:
glDrawElements(GL_TRIANGLES, vertexBuffer.totalIndices, GL_UNSIGNED_INT, (void *)0);
However, the vertexBuffer.totalIndices is equal to the number of VertexIndices structs you've got, which is equal to the total number of indices / 3 (or total number of triangles). So you need to do one of the following:
Easy fix yet stupid: glDrawElements(..., vertexBuffer.totalIndices * 3, ...);
Proper yet more work: vertexBuffer.totalIndices should contain the actual total number of indices that you've got, not the total number of triangles you're rendering.
You need to do one of these because right now totalIndices contains the total number VertexIndices you've got, and each one has 3 indices. The right thing to do here is either rename totalIndices to totalTriangles, or keep track of the actual total number of indices somewhere.
My question is in the code below. I'd like to understand if there's such a thing as "retaining" when it comes to "unsigned char" pointers. Please explain.
// MyObject.h
#interface myObject : NSObject {
unsigned char *myData;
}
// MyObject.m
-(void)makeNewData
{
if (myData) { free(myData); }
myData = [self createBitmapContextData:myCGImageRef];
//Here is my question: do I need a "retain" call equivalent on the next line?
//[myData retain];
}
- (unsigned char*)createBitmapContextData:(CGImageRef)fromImage
{
CGContextRef cgctx = [self createARGBBitmapContextFromImage:myCGImage];
if (cgctx == NULL) { return nil; }
size_t w = CGImageGetWidth(myCGImage);
size_t h = CGImageGetHeight(myCGImage);
CGRect rect = {{0,0},{w,h}};
CGContextDrawImage(cgctx, rect, myCGImage);
unsigned char* data = CGBitmapContextGetData (cgctx);
CGContextRelease(cgctx);
return data;
}
No, there is no such thing as “retaining” a raw pointer.
As mackross says, NSData can be used to hold on to data when you allocate it yourself. However, in this case, you don’t own the data, you‘re merely “getting” it from the CGContext, which owns it. Your reference becomes invalid when the CGContext is released. In this case, you need to own a reference to the context until you no longer need the pointer.