GetNextInterestingTime() gets the same frame multiple times - quicktime

I want to export every frame in a *.mov-Movie-File, so I do this:
GoToBeginningOfMovie(movie);
TimeValue startPoint = 0;
long gnitFrames = 0;
while (startPoint >= 0) {
GetMovieNextInterestingTime(movie, nextTimeStep, 0, &whichMediaType, startPoint, 0, &startPoint, NULL);
gnitFrames++;
}
the problem is, the count of gnitFrames is different (many more) than when I call this:
Track track = GetMovieIndTrack(movie, 1);
Media media = GetTrackMedia(track);
OSType mediatype;
MediaHandler mediahandler = GetMediaHandler(media);
GetMediaHandlerDescription(media, &mediatype, nil, nil);
MediaGetName(mediahandler, medianame, 0, nil);
long nsamples = GetMediaSampleCount(media);
nsamples gives me the correct frame-count. So now my question: how can I do that to get to every frame in a movie just once? (When I export the frame now after I called GetNextInterestingTime, a frame is exported multiple times, sometimes even 25 times)
My operating system is Windows XP.

Using nextTimeStep might be problematic as a timestep does not necessarily have to match a (video) media sample causing GetMovieNextInterestingTime() to return superfluous time stamps.
If all you want to do is to count / locate all frames in a video media, try using nextTimeMediaSample along with GetMediaNextInterestingDisplayTime() on the video Media like this:
...
TimeValue64 start = 0;
TimeValue64 sample_time = 0;
TimeValue64 sample_duration = -1;
int frames = 0;
while( sample_time != -1 ) {
GetMediaNextInterestingDisplayTime( media, nextTimeMediaSample | nextTimeEdgeOK, start, fixed1, &sample_time, &sample_duration );
if( sample_time != -1 ) {
++frames;
}
...
start += sample_duration;
}
...
Caveat:
According to the Q&A article below this approach is not supposed to work out for f.e. MPEG
but for many other formats it works like a charm in my experience.
Technical Q&A QTMTB54: How do I count the frames in an MPEG movie?

Related

How to send cluster in separated node ros pcl

Hi i'm new in pointcloud library. I'm trying to show clustering result point on rviz or pcl viewer, and then show nothing. And i realize that my data show nothing too when i subcsribe and cout that. Hopefully can help my problem, thanks
This is my code for clustering and send node
void cloudReceive(const sensor_msgs::PointCloud2ConstPtr& inputMsg){
mutex_lock.lock();
pcl::fromROSMsg(*inputMsg, *inputCloud);
cout<<inputCloud<<endl;
pcl::search::KdTree<pcl::PointXYZRGB>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZRGB>);
tree->setInputCloud(inputCloud);
std::vector<pcl::PointIndices> cluster_indices;
pcl::EuclideanClusterExtraction<pcl::PointXYZRGB> ec;
ec.setClusterTolerance(0.03);//2cm
ec.setMinClusterSize(200);//min points
ec.setMaxClusterSize(1000);//max points
ec.setSearchMethod(tree);
ec.setInputCloud(inputCloud);
ec.extract(cluster_indices);
if(cluster_indices.size() > 0){
std::vector<pcl::PointIndices>::const_iterator it;
int i = 0;
for (it = cluster_indices.begin(); it != cluster_indices.end(); ++it){
if(i >= 10)
break;
cloud_cluster[i]->points.clear();
std::vector<int>::const_iterator idx_it;
for (idx_it = it->indices.begin(); idx_it != it->indices.end(); idx_it++)
cloud_cluster[i]->points.push_back(inputCloud->points[*idx_it]);
cloud_cluster[i]->width = cloud_cluster[i]->points.size();
// cloud_cluster[i]->height = 1;
// cloud_cluster[i]->is_dense = true;
cout<<"PointCloud representing the Cluster: " << cloud_cluster[i]->points.size() << " data points"<<endl;
std::stringstream ss;
ss<<"cobaa_pipecom2_cluster_"<< i << ".pcd";
writer.write<pcl::PointXYZRGB> (ss.str(), *cloud_cluster[i], false);
pcl::toROSMsg(*cloud_cluster[i], outputMsg);
// cout<<"data = "<< outputMsg <<endl;
cloud_cluster[i]->header.frame_id = FRAME_ID;
pclpub[i++].publish(outputMsg);
// i++;
}
}
else
ROS_INFO_STREAM("0 clusters extracted\n");
}
And this one is the main
int main(int argc, char** argv){
for (int z = 0; z < 10; z++) {
// std::cout << " - clustering/" << z << std::endl;
cloud_cluster[z] = pcl::PointCloud<pcl::PointXYZRGB>::Ptr(new pcl::PointCloud<pcl::PointXYZRGB>);
cloud_cluster[z]->height = 1;
cloud_cluster[z]->is_dense = true;
// cloud_cluster[z]->header.frame_id = FRAME_ID;
}
ros::init(argc,argv,"clustering");
ros::NodeHandlePtr nh(new ros::NodeHandle());
pclsub = nh->subscribe("/pclsegmen",1,cloudReceive);
std::string pub_str("clustering/0");
for (int z = 0; z < 10; z++) {
pub_str[11] = z + 48;//48=0(ASCII)
// z++;
pclpub[z] = nh->advertise <sensor_msgs::PointCloud2> (pub_str, 1);
}
// pclpub = nh->advertise<sensor_msgs::PointCloud2>("/pclcluster",1);
ros::spin();
}
This isn't an exact answer, but I think it addresses your issue & may ease your debugging.
RViz can directly subscribe to a published point cloud, the one I'm assuming you're trying to see in the cloud_receive callback. If you set the Frame to whichever frame it's being published at, and add it from the available topics, you should see the points. (Easier than trying to rebroadcast it as different topics).
Also, I recommend looking at the rostopic command line tool. You can do rostopic list to check if it's being published, rostopic bw to see if it's really publishing the expected volume of data (ex bytes vs kilobytes vs megabytes), rostopic hz to see how frequently (if ever) it's publishing, and (briefly) rostopic echo to look at the data itself. (This is me assuming from your question it's more an issue with the data coming into your node).
If you're having trouble, not with data coming into the node, nor with the visualization of pointcloud data in general, but with the transformed data that's supposed to come out of the node, I would check that the clustering worked, & reduce your code moreso to just having 1 publisher publish something. You may be doing something weird. Like messing up your pointers. You could also turn on stronger compilation warnings for your node with -Wall -Wextra -Werror or step through the execution of it via gdb (launch-prefix="xterm -e gdb --args").
The solution is, i change the ASCII number into lexical_cast. Thanks for your response, i hope this can help other
for (int z = 0; z < CLOUD_QTD; z++) {
// pub_str[11] = z + 48;
std::string topicName = "/pclcluster/" + boost::lexical_cast<std::string>(z);
global::pub[z] = n.advertise <sensor_msgs::PointCloud2> (topicName, 1);
}

ParamValidationExt error with WelsInitEncoderExt failed while setting up OpenH264 encoder

Scenario:
I am using OpenH264 with my App to encode into a video_file.mp4.
Environment:
Platform : MacOs Sierra
Compiler : Clang++
The code:
Following is the crux of the code I have:
void EncodeVideoFile() {
ISVCEncoder * encoder_;
std:string video_file_name = "/Path/to/some/folder/video_file.mp4";
EncodeFileParam * pEncFileParam;
SEncParamExt * pEnxParamExt;
float frameRate = 1000;
EUsageType usageType = EUsageType::CAMERA_VIDEO_REAL_TIME;
bool denoise = false;
bool lossless = true;
bool enable_ltr = false;
int layers = 1;
bool cabac = false;
int sliceMode = 1;
pEncFileParam = new EncodeFileParam;
pEncFileParam->eUsageType = EUsageType::CAMERA_VIDEO_REAL_TIME;
pEncFileParam->pkcFileName = video_file_name.c_str();
pEncFileParam->iWidth = frame_width;
pEncFileParam->iHeight = frame_height;
pEncFileParam->fFrameRate = frameRate;
pEncFileParam->iLayerNum = layers;
pEncFileParam->bDenoise = denoise;
pEncFileParam->bLossless = lossless;
pEncFileParam->bEnableLtr = enable_ltr;
pEncFileParam->bCabac = cabac;
int rv = WelsCreateSVCEncoder (&encoder_);
pEnxParamExt = new SEncParamExt;
pEnxParamExt->iUsageType = pEncFileParam->eUsageType;
pEnxParamExt->iPicWidth = pEncFileParam->iWidth;
pEnxParamExt->iPicHeight = pEncFileParam->iHeight;
pEnxParamExt->fMaxFrameRate = pEncFileParam->fFrameRate;
pEnxParamExt->iSpatialLayerNum = pEncFileParam->iLayerNum;
pEnxParamExt->bEnableDenoise = pEncFileParam->bDenoise;
pEnxParamExt->bIsLosslessLink = pEncFileParam->bLossless;
pEnxParamExt->bEnableLongTermReference = pEncFileParam->bEnableLtr;
pEnxParamExt->iEntropyCodingModeFlag = pEncFileParam->bCabac ? 1 : 0;
for (int i = 0; i < pEnxParamExt->iSpatialLayerNum; i++) {
pEnxParamExt->sSpatialLayers[i].sSliceArgument.uiSliceMode = pEncFileParam->eSliceMode;
}
encoder_->InitializeExt(pEnxParamExt);
int videoFormat = videoFormatI420;
encoder_->SetOption (ENCODER_OPTION_DATAFORMAT, &videoFormat);
int frameSize = frame_width * frame_height * 3 / 2;
int total_num = 500;
BufferedData buf;
buf.SetLength (frameSize);
// check the buffer before proceeding
if (buf.Length() != (size_t)frameSize) {
CloseEncoder();
return;
}
SFrameBSInfo info;
memset (&info, 0, sizeof (SFrameBSInfo));
SSourcePicture pic;
memset (&pic, 0, sizeof (SSourcePicture));
pic.iPicWidth = frame_width;
pic.iPicHeight = frame_height;
pic.iColorFormat = videoFormatI420;
pic.iStride[0] = pic.iPicWidth;
pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1;
pic.pData[0] = buf.data();
pic.pData[1] = pic.pData[0] + frame_width * frame_height;
pic.pData[2] = pic.pData[1] + (frame_width * frame_height >> 2);
for(int num = 0; num < total_num; num++) {
// try to encode the frame
rv = encoder_->EncodeFrame (&pic, &info);
}
if (encoder_) {
encoder_->Uninitialize();
WelsDestroySVCEncoder (encoder_);
}
}
Above code is something I pulled up from official usage examples of OpenH264 where BufferedData.h is a class I reused from OpenH264 utils
Issue:
But, I am getting the following error:
[OpenH264] this = 0x0x1038bc8c0, Error:ParamValidationExt(), width > 0, height > 0, width * height <= 9437184, invalid 0 x 0 in dependency layer settings!
[OpenH264] this = 0x0x1038bc8c0, Error:WelsInitEncoderExt(), ParamValidationExt failed return 2.
[OpenH264] this = 0x0x1038bc8c0, Error:CWelsH264SVCEncoder::Initialize(), WelsInitEncoderExt failed.
Above does not crash the application but it goes through a blank run without creating the video_file.mp4 with the dummy data that I am trying to write into it.
Question:
There seems to be something wrong with the set up config I applying to pEnxParamExtwhich goes into encoder_->InitializeExt.
What am I doing wrong with the set up of the encoder?
Note:
I am not trying to hook up to any camera device. I am just trying to create a .mp4 video out of some dummy image data.
If you want to get complete and working OpenH264 Encoder Initialization procedure you can click... here.
According to your problem scenario, you are trying to create a video file(.mp4/.avi) from some dummy images. This task can be accomplished using two different libraries: i) Library for Codec, ii) Library for Container.
i) Library for Codec: It's so much easy to use a OpenH264 to compress data. One thing I must mention is that, OpenH264 always works with raw frames e.g. yuv420 data. So, if you want to compress your image data, you have to convert these image data into yuv420 color format. To get OpenH264 click... here
ii) Library for Container: After getting the encoded data you have to use another library to create the container with extension .mp4, .avi, .flv etc. There exists a lot of libraries in github to do that staff like FFmpeg, OpenCV, Bento4, MP4Maker, mp4parser etc. Before using these libraries please check in detail about the license issues. If you use FFmpeg, you will not need to use OpenH264 becuse FFmpeg itself works along with several codecs. You will also find lot more working examples as so many developers are working with video data out there.
Hope it helps. :)

How do I print image on their original size in Excel using libxlsxwriter?

I just started to use libxlsxwriter to create excel sheet. My problem is I don't know how to centralized the picture and how to print pictures on their original size. Just consider the set having only 3 columns and each column having different size for example 30,10,20. I need to know what is the calculation do I need to find offset and scale values.
lxw_image_options options = {.x_offset = 0, .y_offset = 0,.x_scale = 0.9, .y_scale = 0.9};
worksheet_insert_image_opt(worksheet, row, 0, img_path, &options);
With that, I need to Know How many rows the picture can hold.Then only I can create the upcoming set without overlap.
how to print pictures on their original size
libxlsxwriter inserts images into an xlsx file at their original size based on the width, height and DPI information in the image. It should insert images in exactly the same way as if you did it in Excel via the user interface.
However, images in OpenOffice or LibreOffice may not appear to the correct size. This isn't an libxlsxwriter issue: the same thing happens with Excel.
To insert images at precise positions you will need to know the dimensions of the images in pixels and also the DPI since Excel scales according to it's default DPI (usually 96) Here is an example of inserting two images in a row:
/*
* An example of inserting images with the libxlsxwriter library.
*/
#include "xlsxwriter.h"
int main() {
/* Create a new workbook and add a worksheet. */
lxw_workbook *workbook = workbook_new("demo.xlsx");
lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL);
lxw_image_options options = {.x_offset = 0, .y_offset = 0,
.x_scale = 1, .y_scale = 1};
double default_dpi = 96.0;
double image_dpi = 90.0;
int32_t image_height = 138;
int32_t image_offset = (int32_t)(image_height * default_dpi/image_dpi);
/* Insert the first image. */
worksheet_insert_image(worksheet, 1, 2, "logo.png");
/* Insert the second image relative to the first. */
options.y_offset += image_offset;
worksheet_insert_image_opt(worksheet, 1, 2, "logo.png", &options);
workbook_close(workbook);
return 0;
}
Output:
How many rows did picture hold?
I want to Present my findings here to improvise the result.
int row=1;
lxw_image_options options = {.x_offset = 0, .y_offset = 0};
worksheet_insert_image_opt(worksheet, row, 2,"logo.png", &options);
row+=(options.height/worksheet->default_row_pixels);
Here I used the variable options.height to calculate How many rows the picture hold. The libxlsxwriter did read the height from the image file in pixels. It uses struct variable option only for read initialized variable it will never write anything in the set. But I did by adding the line user_options->height=options->height; in the function worksheet_insert_image_opt at worksheet.c.
lxw_error worksheet_insert_image_opt(lxw_worksheet *self,
lxw_row_t row_num, lxw_col_t col_num,
const char *filename,
lxw_image_options *user_options)
{
FILE *image_stream;
char *short_name;
lxw_image_options *options;
if (!filename) {
LXW_WARN("worksheet_insert_image()/_opt(): "
"filename must be specified.");
return LXW_ERROR_NULL_PARAMETER_IGNORED;
}
/* Check that the image file exists and can be opened. */
image_stream = fopen(filename, "rb");
if (!image_stream) {
LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
"file doesn't exist or can't be opened: %s.",
filename);
return LXW_ERROR_PARAMETER_VALIDATION;
}
/* Get the filename from the full path to add to the Drawing object. */
short_name = lxw_basename(filename);
if (!short_name) {
LXW_WARN_FORMAT1("worksheet_insert_image()/_opt(): "
"couldn't get basename for file: %s.", filename);
fclose(image_stream);
return LXW_ERROR_PARAMETER_VALIDATION;
}
/* Create a new object to hold the image options. */
options = calloc(1, sizeof(lxw_image_options));
if (!options) {
fclose(image_stream);
return LXW_ERROR_MEMORY_MALLOC_FAILED;
}
if (user_options) {
memcpy(options, user_options, sizeof(lxw_image_options));
options->url = lxw_strdup(user_options->url);
options->tip = lxw_strdup(user_options->tip);
}
/* Copy other options or set defaults. */
options->filename = lxw_strdup(filename);
options->short_name = lxw_strdup(short_name);
options->stream = image_stream;
options->row = row_num;
options->col = col_num;
if (!options->x_scale)
options->x_scale = 1;
if (!options->y_scale)
options->y_scale = 1;
if (_get_image_properties(options) == LXW_NO_ERROR) {
user_options->height=options->height;
STAILQ_INSERT_TAIL(self->image_data, options, list_pointers);
return LXW_NO_ERROR;
}
else {
free(options);
return LXW_ERROR_IMAGE_DIMENSIONS;
}
}
this how I was calcuating rows. If there is a better way please let me know.

How to know when AVPlayerItem is buffered to the end of a song

I'm trying to determine the best way to tell if an AVPlayerItem is buffered to the end of the stream. Not just that the buffer is full, but that the buffer contains everything needed to play the rest of the item without additional buffering. AVPlayerItem offers a is isPlaybackBufferFull call, but that doesn't tell me if any additional buffering needs to happen before the item is finished playing.
My current plan is to combine that with preferredForwardBufferDuration to check if the item will ever need to buffer more, but is this the best way?
For instance:
- (void)observeValueForKeyPath:(NSString*)aKeyPath ofObject:(id)aObject change:(NSDictionary*)aChange context:(void*)aContext
{
if( [aKeyPath isEqualToString:#"playbackBufferFull"] )
{
CMTime theBufferTime = CMTimeMakeWithSeconds( self.currentItem.preferredForwardBufferDuration, 1 );
CMTime theEndBufferTime = CMTimeAdd( self.currentItem.currentTime, theBufferTime );
if( CMTimeCompare( theEndBufferTime, self.currentItem.duration ) >= 0 )
{
// Buffered to the end
}
}
}
I found a pretty good solution to this problem that can be seen below. The proposed solution written in the question didn't really work well because the preferredForwardBufferDuration is set to 0 by default which pretty much makes the solution not viable.
The following the code works pretty well. I call it every second on a timer.
auto theLoadedRanges = self.currentItem.loadedTimeRanges;
CMTime theTotalBufferedDuration = kCMTimeZero;
for( NSValue* theRangeValue in theLoadedRanges )
{
auto theRange = [theRangeValue CMTimeRangeValue];
theTotalBufferedDuration = CMTimeAdd( theTotalBufferedDuration, theRange.duration );
}
auto theDuration = CMTimeGetSeconds( self.currentItem.duration );
if( theDuration > 0 )
{
float thePercent = CMTimeGetSeconds( theTotalBufferedDuration ) / theDuration;
if( thePercent >= 0.99f )
{
// Fully buffered
}
}

Grouped Models Render Slowly in iOS (OpenGL ES 2.0)

First, let me clarify what I mean by "grouped models." I'm not actually sure what the standard terminology for this is. In order to reduce the number of rendering calls, I am grouping multiple models into a single model, and rendering the whole thing with a single call to glDrawElements (using VBOs). In my code, I call this a ModelGroup. I use it for various things, but especially for large groups of geometrically simple objects (like buildings in a city, or particles).
The problem has recently surfaced where my ModelGroups are rendering very slowly. I have isolated the slowdown to the actual call to glDrawElements by putting a timer around it. For instance, my particles used to render ~10k particles (without instancing) at around 2ms or so. I can't recall the exact number, but let's just say the rendering was definitely not the bottleneck as it currently is. As of now, a single call to glDrawElements with 10k particles takes right about 256ms. This performance is only marginally better than rendering the objects each with separate calls to glDrawElements. So, there is clearly a massive burden on the GPU for some reason.
What has changed in my engine:
I recently updated XCode and changed from using EAGLView to using GLKViewController. I changed nothing else in my code between these two very different states of performance. I will say that, in order to migrate over to the use of the GLKViewController, I recreated my project entirely and added all of my source files in. Then I rewrote my game loop to be updated by the GLKViewController's update function. This was a very minor change, however.
Just to be completely clear on what my ModelGroup class does, I will post the function that compiles the added models into the display model which is rendered.
-(bool) compileDisplayModelWithNormals:(bool)updateNormals withTexcoords:(bool)updateTexcoords withColor:(bool)updateColor withElements:(bool)updateElements
{
modelCompiled = YES;
bool initd = displayModel->positions;
// set properties
if( !initd )
{
displayModel->primType = GL_UNSIGNED_SHORT;
displayModel->elementType = GL_TRIANGLES;
displayModel->positionType = GL_FLOAT;
displayModel->texcoordType = GL_FLOAT;
displayModel->colorType = GL_FLOAT;
displayModel->normalType = GL_FLOAT;
displayModel->positionSize = 3;
displayModel->normalSize = 3;
displayModel->texcoordSize = 2;
displayModel->colorSize = 4;
// initialize to zero
displayModel->numVertices = 0;
displayModel->numElements = 0;
displayModel->positionArraySize = 0;
displayModel->texcoordArraySize = 0;
displayModel->normalArraySize = 0;
displayModel->elementArraySize = 0;
displayModel->colorArraySize = 0;
// sum the sizes
for( NSObject<RenderedItem> *ri in items )
{
GLModel *model = ri.modelAsset.model.displayModel;
displayModel->numVertices += model->numVertices;
displayModel->numElements += model->numElements;
displayModel->positionArraySize += model->positionArraySize;
displayModel->texcoordArraySize += model->texcoordArraySize;
displayModel->normalArraySize += model->normalArraySize;
displayModel->elementArraySize += model->elementArraySize;
displayModel->colorArraySize += model->colorArraySize;
}
displayModel->positions = (GLfloat *)malloc( displayModel->positionArraySize );
displayModel->texcoords = (GLfloat *)malloc( displayModel->texcoordArraySize );
displayModel->normals = (GLfloat *)malloc( displayModel->normalArraySize );
displayModel->elements = (GLushort *)malloc( displayModel->elementArraySize );
displayModel->colors = (GLfloat *)malloc( displayModel->colorArraySize );
}
// update the data
int vertexOffset = 0;
int elementOffset = 0;
for( int j = 0; j < [items count]; j++ )
{
NSObject<RenderedItem> *ri = (GameItem *)[items objectAtIndex:j];
GLModel *model = ri.modelAsset.model.displayModel;
if( !ri.geometryUpdate )
{
vertexOffset += model->numVertices;
continue;
}
// reset the update flag
ri.geometryUpdate = NO;
// get GameItem transform data
rpVec3 pos = [ri getPosition];
rpMat3 rot = [ri orientation];
int NoV = model->numVertices;
int NoE = model->numElements;
for( int i = 0; i < NoV; i++ )
{
// positions
rpVec3 r = rpVec3( model->positions, model->positionSize * i );
// scale
rpVec3 s = ri.scale;
r.swizzleLocal( s );
// rotate
r = rot * r;
// translate
r.addLocal( pos );
int start = model->positionSize * (vertexOffset + i);
for( int k = 0; k < model->positionSize; k++ )
displayModel->positions[start + k] = r[k];
if( updateTexcoords )
{
// texcoords
start = model->texcoordSize * (vertexOffset + i);
if( model->texcoords )
for( int k = 0; k < model->texcoordSize; k++ )
displayModel->texcoords[start + k] = model->texcoords[model->texcoordSize * i + k];
}
if( updateNormals )
{
// normals (need to be rotated)
if( model->normals )
{
for( int k = 0; k < model->normalSize; k++ )
{
rpVec3 vn = rpVec3( model->normals, model->normalSize * i );
rpVec3 vnRot = rot * vn;
start = model->normalSize * (vertexOffset + i);
displayModel->normals[start + k] = vnRot[k];
}
}
}
if( updateColor )
{
if( model->colors )
{
start = model->colorSize * (vertexOffset + i);
displayModel->colors[start] = ri.color.r;
displayModel->colors[start + 1] = ri.color.g;
displayModel->colors[start + 2] = ri.color.b;
displayModel->colors[start + 3] = ri.color.a;
}
}
}
if( updateElements )
{
for( int i = 0; i < NoE; i++ )
{
// elements
displayModel->elements[elementOffset + i] = model->elements[i] + vertexOffset;
}
}
vertexOffset += NoV;
elementOffset += NoE;
}
return YES;
}
Just to be complete, here is how I render the particles. Inside the particle field draw function:
glBindVertexArray( modelGroup.displayModel->modelID );
glBindTexture( GL_TEXTURE_2D, textureID );
// set shader program
if( changeShader ) glUseProgram( shader.programID );
[modelViewStack push];
mtxMultiply( modelViewProjectionMatrix.m, [projectionStack top].m, [modelViewStack top].m );
glUniformMatrix4fv( shader.modelViewProjectionMatrixID, 1, GL_FALSE, modelViewProjectionMatrix.m );
[DebugTimer check:#"particle main start"];
glDrawElements( GL_TRIANGLES, modelGroup.displayModel->numElements, GL_UNSIGNED_SHORT, 0 );
[DebugTimer check:#"particle main end"];
[modelViewStack pop];
The two statements that sandwich the glDrawElements statement are the timer I used to measure time between events.
Also, I just wanted to add that I have run on both the device and the iPad simulator 6.1 with the same result. The simulator is slower at performing multiple draw calls, but both are equally slow at calling glDrawElements for a ModelGroup. As far as hardware acceleration is concerned, I have checked to make sure that this performance hit isn't coming as some side effect of a lack of acceleration. I rendered a model read in from a file which contained 1024 cubes (similar to a ModelGroup for a city) which rendered with no problem (no 20ms delay as with 1000 cubes in a ModelGroup).
I believe that I have solved the mystery, in a manner of speaking. It is, after all, still a mystery to me why this solves the problem.
I had been using my own custom enum values for these functions
glEnableVertexAttribArray
glVertexAttribPointer
instead of using the newly (as of iOS 5.0, I think) Apple-specified values that came as part of the GLKViewController class:
GLKVertexAttribPosition
GLKVertexAttribNormal
GLKVertexAttribTexcoord0
Having made this change yielded the kind of performance I expected when calling glDrawElements. My model groups will now render on the order of 0.1 ms as they should, rather than ~20 ms. As I said, I really do not understand exactly why this fixes anything, but it's a solution all the same.

Resources