Handmade Hero » Forums » Code » Main game loop on OS X
mmozeiko
Mārtiņš Možeiko
1923 posts / 1 project
#8259 Main game loop on OS X
2 years, 8 months ago Edited by Mārtiņš Možeiko on Aug. 26, 2016, 6:47 p.m.

kCGImageAlphaNoneSkipFirst and kCGBitmapByteOrder32Little are explained here: https://developer.apple.com/libra...l#//apple_ref/c/tdef/CGBitmapInfo

kCGImageAlphaNoneSkipFirst
There is no alpha channel. If the total size of the pixel is greater than the space required for the number of color components in the color space, the most significant bits are ignored.

kCGBitmapByteOrder32Little
32-bit, little endian format.

*Pixel++ = Red | Green << 8 | Blue << 16
This operation consists of 3 parts:
Red
Green << 8
Blue << 16

What these expressions do is take a byte (8-bits) and shift it to correct position in integer (32-bits).
So if you have byte RR (8-bits) then as integer it will look like 000000RR (upper 24 bits are 0).
Green << 8 takes byte GG (8-bits) shifts it 8 bits to right and gets you 0000GG00 (upper 16 bits are 0 and lower 8 bits are also 0).
Similarly with Blue << 16, take a byte BB, shift it up 16 bits and that gets you 00BB0000 (upper 8 bits and lower 16 are 0).

Now take all three values and or them together (remember that OR gives you result 1 if any of inputs are 1).
1
2
3
4
5
000000RR
0000GG00
00BB0000
--------
00BBGGRR

Last line is what you get with "Red | Green << 8 | Blue << 16" expression.


"Red | Green | Blue | xx" will get you only 1 byte of value:
1
2
3
4
5
6
000000RR
000000GG
000000BB
000000xx
--------
000000??

Where ?? is all bits of R, G, B and x or'ed together.



adge
Adrian
46 posts

None

#8262 Main game loop on OS X
2 years, 8 months ago

Perfect explanation! Thank you very much. It clicked right away!

None
adge
Adrian
46 posts

None

#8400 Main game loop on OS X
2 years, 8 months ago

I just implemented a performance counter using mach_absolute_time() and mach_timebase_info().

This is my code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
while (running)
  {
    uint64 LastCounter = mach_absolute_time();
    static mach_timebase_info_data_t    sTimebaseInfo;

    if ( sTimebaseInfo.denom == 0 )
    {
        (void) mach_timebase_info(&sTimebaseInfo);
    }

    //do Stuff

    uint64 EndCounter = mach_absolute_time();
    uint64 CyclesElapsed = EndCounter - LastCounter;
    uint64 NanosecondsElapsed = CyclesElapsed * sTimebaseInfo.numer / sTimebaseInfo.denom;
    float SecondsElapsed = NanosecondsElapsed / 1000000000.0;
    int FPS = ( float )( 1000000000.0 / NanosecondsElapsed );

    printf("CyclesElapsed: %llu, TimeElapsed: %f, FPS: %f\n", CyclesElapsed, SecondsElapsed, FPS);
    LastCounter = EndCounter;
  }


However for some reason I get 14-16 FPS. That seems quite low to me. there is nothing going on there which could slow things down. Casey got at least 140 so whats wrong there? Are my calculations wrong?
How would I ever reach 30 or even 60 with a ton of calculations going on?

None
mmozeiko
Mārtiņš Možeiko
1923 posts / 1 project
#8401 Main game loop on OS X
2 years, 8 months ago

It depends on what is in "//do Stuff". If you are doing software renderer, then 14fps might be normal value depending on how powerful is your Mac hardware. It also depends if multithreaded rendering enabled? And are compiler optimizations enabled?
adge
Adrian
46 posts

None

#8402 Main game loop on OS X
2 years, 8 months ago Edited by Adrian on Sept. 3, 2016, 8:24 p.m.

No there is nothing enabled. This is what I'm compiling with:

1
clang -Wall -framework Cocoa -o build/main code/main.m


Moreover what is the difference between QueryPerformanceCounter and rdtsc? Casey is implementing rdtsc right after he got the FPS through the QueryPerformanceCounter. But as far as I understand it, they both get you the CPU cycles elapsed on call.

So adding -O2 for optimization changes nothing.

The code of my while loop:
1
2
3
    ProcessEvents();
    RenderWeirdGradient(&GlobalBackbuffer, XOffset, YOffset);
    [view setNeedsDisplay:YES];


ProcessEvents():
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void ProcessEvents() {
  @autoreleasepool {
    NSEvent *event;
    int speed = 255/20;
    do {
      event = [NSApp nextEventMatchingMask: NSAnyEventMask
      untilDate: nil
      inMode: NSDefaultRunLoopMode
      dequeue: YES];
      if (!event) {
        //break;
      }
      switch ([event type]) {
        case NSKeyUp:
        case NSKeyDown: {
          int hotkeyMask = NSCommandKeyMask | NSAlternateKeyMask | NSControlKeyMask | NSAlphaShiftKeyMask;
          if ([event modifierFlags] & hotkeyMask) {
            // Handle events like cmd+q etc
            [NSApp sendEvent:event];
            break;
          }
          // Handle normal keyboard events in place.
          int isDown = ([event type] == NSKeyDown);
          switch ([event keyCode]) {
            case 13: { // W

            } break;
            case 0: { // A

            } break;
            case 1: { // S

            } break;
            case 2: { // D

            } break;
            case 12: { // Q

            } break;
            case 14: { // E

            } break;
            case 126: { // Up
              YOffset = YOffset + speed;
            } break;
            case 123: { // Left
              XOffset = XOffset + speed;
            } break;
            case 125: { // Down
              YOffset = YOffset - speed;
            } break;
            case 124: { // Right
              XOffset = XOffset - speed;
            } break;
            case 53: { // Esc

            } break;
            case 49: { // Space

            } break;

            default: {
              // Uncomment to learn your keys:
              //NSLog(@"Unhandled key: %d", [event keyCode]);
            } break;
          }
        } break;
        default: {
          // Handle events like app focus/unfocus etc
          [NSApp sendEvent:event];
        } break;
      }
    } while (event);
  }
}


RenderWeirdGradient():
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
internal void RenderWeirdGradient(osx_offscreen_buffer *Buffer, int BlueOffset, int GreenOffset)
{
int Pitch = Buffer->Width * Buffer->BytesPerPixel;

uint8 *Row = (uint8 *)Buffer->Memory;
for(int Y = 0;
Y < Buffer->Height;
++Y)
{
  uint32 *Pixel = (uint32 *)Row;
  for(int X = 0;
    X < Buffer->Width;
    ++X)
    {
      uint8 Red = 0;
      uint8 Green = (Y + GreenOffset);
      uint8 Blue = (X + BlueOffset);

      *Pixel++ = Red | Green << 8 | Blue << 16 ;

      // RR GG BB xx
      // xx BB GG RR
    }

    Row += Pitch;
  }
}


Even if I comment the rendering function out the program doesn't run any faster.

But if I comment [view setNeedsDisplay:YES] out, the **** hits the fan. I suddenly get 2000-4000 FPS. What is going on there? Why is [view setNeedsDisplay:YES] slowing the program that hard down?

None
mmozeiko
Mārtiņš Možeiko
1923 posts / 1 project
#8404 Main game loop on OS X
2 years, 8 months ago

Difference between QueryPerformanceCounter and rdtsc is that QPC can be used for time measurements, but rdtsc not. rdtsc counter can vary depending on CPU speed. QPC is always adjusted by OS to be independent of CPU speed.

On OSX QueryPerformanceCounter equivalent is mach_absolute_time. And __rdtsc equivalent is __builtin_readcyclecounter.

Not sure exactly, but from documentation it seems that [view setNeedsDisplay:YES] informs OS that it needs to redrawn. If you comment it out, the performance should increases because there is a less work to do. But does it actually display anything then? I'm not very familiar with OSX.
Flyingsand
78 posts
#8406 Main game loop on OS X
2 years, 8 months ago

Could you post your view's implementation of -drawRect:? You could also place performance measurements in various spots within -drawRect: to see what inside that method is hogging the time.
adge
Adrian
46 posts

None

#8407 Main game loop on OS X
2 years, 8 months ago Edited by Adrian on Sept. 4, 2016, 9:01 a.m.

This is the code for drawRect:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
- (void)drawRect:(NSRect)dirtyRect
{
  CGContextRef gctx = [[NSGraphicsContext currentContext] graphicsPort];
  CGRect myBoundingBox;
  myBoundingBox = CGRectMake(0, 0, GlobalBackbuffer.Width, GlobalBackbuffer.Height);

  CGImageRef backImage = CGBitmapContextCreateImage(backbuffer);


  CGContextDrawImage(gctx, myBoundingBox, backImage);
  CGImageRelease(backImage);
}


And this is my View's setup code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (id)initWithFrame:(NSRect)frameRect
{
  self = [super initWithFrame:frameRect];
  if (self)
  {
    int bitmapBytesPerRow;
    GlobalBackbuffer.Width = window.frame.size.width;
    GlobalBackbuffer.Height = window.frame.size.height;
    GlobalBackbuffer.BytesPerPixel = 4;

    int BitmapMemorySize = GlobalBackbuffer.Width * GlobalBackbuffer.BytesPerPixel * GlobalBackbuffer.Height;
    GlobalBackbuffer.Memory = mmap(0,
                                   BitmapMemorySize,
                                   PROT_WRITE |
                                   PROT_READ,
                                   MAP_ANON |
                                   MAP_PRIVATE,
                                   -1,
                                   0);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    bitmapBytesPerRow = GlobalBackbuffer.Width * 4;
    backbuffer = CGBitmapContextCreate(GlobalBackbuffer.Memory,
                                       GlobalBackbuffer.Width,
                                       GlobalBackbuffer.Height,
                                       8,
                                       bitmapBytesPerRow,
                                       colorSpace,
                                       kCGImageAlphaNoneSkipLast| kCGBitmapByteOrder32Little);
    CGColorSpaceRelease(colorSpace);

  }
  return self;
}



I wrote some code to call [view setNeedsDisplay:YES] every tenth cycle and this is what the console prints out:
1
2
3
4
5
6
7
8
9
CyclesElapsed: 383199, TimeElapsed: 0.000383, FPS: 2609.609902
CyclesElapsed: 196884, TimeElapsed: 0.000197, FPS: 5079.133875
CyclesElapsed: 191598, TimeElapsed: 0.000192, FPS: 5219.258875
CyclesElapsed: 191310, TimeElapsed: 0.000191, FPS: 5227.118250
CyclesElapsed: 190641, TimeElapsed: 0.000191, FPS: 5245.462000
CyclesElapsed: 190246, TimeElapsed: 0.000190, FPS: 5256.352625
CyclesElapsed: 189976, TimeElapsed: 0.000190, FPS: 5263.821375
CyclesElapsed: 220344, TimeElapsed: 0.000220, FPS: 4538.356530
CyclesElapsed: 61552480, TimeElapsed: 0.061552, FPS: 16.246296


Maybe its normal that [view SetNeedsDisplay:YES] slows things down?

None
mmozeiko
Mārtiņš Možeiko
1923 posts / 1 project
#8408 Main game loop on OS X
2 years, 8 months ago Edited by Mārtiņš Možeiko on Sept. 4, 2016, 9:55 a.m.

It could be simply that OSX is slow when blitting software pixels to screen. Because nobody is usually doing that the OS is not optimized for that. What happens if you resize window smaller (for example to 400x400 or something like that)? If it gets faster then that's the problem.

It could also be problem of OSX not liking your pixel format and then it does some expensive conversion (but unlikely). Can you try changing kCGImageAlphaNoneSkipLast to kCGImageAlphaNoneSkipFirst?

Anyway the real fix would be to use OpenGL for blitting your pixel buffer to screen.

adge
Maybe its normal that [view SetNeedsDisplay:YES] slows things down?

My guess is that when you comment it out, the drawRect method never gets called. That why it gets faster.
adge
Adrian
46 posts

None

#8409 Main game loop on OS X
2 years, 8 months ago

Yeah I want to change my Code to use OpenGL, but I don't really understand how this OpenGL stuff works yet. I have a resizing bitmap function implemented. So as I'm making the window smaller the FPS go up immediately.

Yeah it seems I will have to use OpenGL due to CoreGraphics blitting seems to be too slow.

None
mmozeiko
Mārtiņš Možeiko
1923 posts / 1 project
#8423 Main game loop on OS X
2 years, 8 months ago

Here's some simple reference code to start with OpenGL:
Create GL context: https://github.com/thedmd/pixelto.../master/PixelToasterApple.mm#L515
Copy pixels from memory to GL texture & draw it to screen: https://github.com/thedmd/pixelto...master/PixelToasterApple.mm#L1018
Handling resizing: https://github.com/thedmd/pixelto.../master/PixelToasterApple.mm#L750
PixelToaster is a cross-platform library that allows you to write pixels in cpu array, and then it blits that to the screen.

This code does a bit more stuff, like allows to use float type for pixel values, but that can easily be stripped out.
Flyingsand
78 posts
#8440 Main game loop on OS X
2 years, 8 months ago Edited by Flyingsand on Sept. 5, 2016, 5:18 p.m.

adge
This is the code for drawRect:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
- (void)drawRect:(NSRect)dirtyRect
{
  CGContextRef gctx = [[NSGraphicsContext currentContext] graphicsPort];
  CGRect myBoundingBox;
  myBoundingBox = CGRectMake(0, 0, GlobalBackbuffer.Width, GlobalBackbuffer.Height);

  CGImageRef backImage = CGBitmapContextCreateImage(backbuffer);


  CGContextDrawImage(gctx, myBoundingBox, backImage);
  CGImageRelease(backImage);
}


And this is my View's setup code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (id)initWithFrame:(NSRect)frameRect
{
  self = [super initWithFrame:frameRect];
  if (self)
  {
    int bitmapBytesPerRow;
    GlobalBackbuffer.Width = window.frame.size.width;
    GlobalBackbuffer.Height = window.frame.size.height;
    GlobalBackbuffer.BytesPerPixel = 4;

    int BitmapMemorySize = GlobalBackbuffer.Width * GlobalBackbuffer.BytesPerPixel * GlobalBackbuffer.Height;
    GlobalBackbuffer.Memory = mmap(0,
                                   BitmapMemorySize,
                                   PROT_WRITE |
                                   PROT_READ,
                                   MAP_ANON |
                                   MAP_PRIVATE,
                                   -1,
                                   0);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    bitmapBytesPerRow = GlobalBackbuffer.Width * 4;
    backbuffer = CGBitmapContextCreate(GlobalBackbuffer.Memory,
                                       GlobalBackbuffer.Width,
                                       GlobalBackbuffer.Height,
                                       8,
                                       bitmapBytesPerRow,
                                       colorSpace,
                                       kCGImageAlphaNoneSkipLast| kCGBitmapByteOrder32Little);
    CGColorSpaceRelease(colorSpace);

  }
  return self;
}



I wrote some code to call [view setNeedsDisplay:YES] every tenth cycle and this is what the console prints out:
1
2
3
4
5
6
7
8
9
CyclesElapsed: 383199, TimeElapsed: 0.000383, FPS: 2609.609902
CyclesElapsed: 196884, TimeElapsed: 0.000197, FPS: 5079.133875
CyclesElapsed: 191598, TimeElapsed: 0.000192, FPS: 5219.258875
CyclesElapsed: 191310, TimeElapsed: 0.000191, FPS: 5227.118250
CyclesElapsed: 190641, TimeElapsed: 0.000191, FPS: 5245.462000
CyclesElapsed: 190246, TimeElapsed: 0.000190, FPS: 5256.352625
CyclesElapsed: 189976, TimeElapsed: 0.000190, FPS: 5263.821375
CyclesElapsed: 220344, TimeElapsed: 0.000220, FPS: 4538.356530
CyclesElapsed: 61552480, TimeElapsed: 0.061552, FPS: 16.246296


Maybe its normal that [view SetNeedsDisplay:YES] slows things down?


Calling -setNeedsDisplay: on a view forces the -drawRect: method to be called, which needs to happen every frame to get any updates that you have made to the back buffer. If you don't call it, -drawRect: is only called at startup when the view needs to draw itself initially. Although it will also get called when OS X senses the view needs to redraw itself due to a change in the view's frame, or a window has overlapped it, etc.

I did a quick test myself with the code you supplied, and I'm getting a fairly consistent 60 fps. Here are sampling of my timings I got from rendering into a 1600 x 900 window with optimizations (-O3):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- CGBitmapContextCreateImage: 44320
- CGContextDrawImage: 10572902
- RenderWeirdGradient: 2187883
total elapsed: 15514618
seconds: 0.015515, fps: 64.455345

- CGBitmapContextCreateImage: 44688
- CGContextDrawImage: 10001166
- RenderWeirdGradient: 2435154
total elapsed: 19695196
seconds: 0.019695, fps: 50.773804

- CGBitmapContextCreateImage: 52355
- CGContextDrawImage: 10374861
- RenderWeirdGradient: 2161021
total elapsed: 15187978
seconds: 0.015188, fps: 65.841553

- CGBitmapContextCreateImage: 44878
- CGContextDrawImage: 9977201
- RenderWeirdGradient: 2225293
total elapsed: 19302945
seconds: 0.019303, fps: 51.805569

- CGBitmapContextCreateImage: 44696
- CGContextDrawImage: 10377475
- RenderWeirdGradient: 2468919
total elapsed: 21063529
seconds: 0.021064, fps: 47.475430

- CGBitmapContextCreateImage: 54630
- CGContextDrawImage: 10288651
- RenderWeirdGradient: 2155761
total elapsed: 15267769
seconds: 0.015268, fps: 65.497452

- CGBitmapContextCreateImage: 47171
- CGContextDrawImage: 10221998
- RenderWeirdGradient: 2153584
total elapsed: 15312094
seconds: 0.015312, fps: 65.307854

- CGBitmapContextCreateImage: 46089
- CGContextDrawImage: 10290942
- RenderWeirdGradient: 2364717
total elapsed: 15629533
seconds: 0.015630, fps: 63.981434


(integer times are in nanoseconds).
So it should be fully possible to achieve 60fps using this method, although it is a little inconsistent. I believe there are some additional optimizations you can do in CoreGraphics/Quartz using CGLayers instead of drawing directly into the context, but I haven't tried that. Ultimately you do want to use CVDisplayLink for the best, most consistent framerate on OS X.

I would time individual calls as I have done to see what is taking up so much time when you're getting that low fps.
adge
Adrian
46 posts

None

#8441 Main game loop on OS X
2 years, 8 months ago Edited by Adrian on Sept. 5, 2016, 7:46 p.m.

Well thats weird. So why am I getting these low FPS then? I'm running it on a mid 2012 MBP retina. Maybe the machine is getting old but its still quite fast.

Are you using the terminal for compilation? If so can you show me the command?

As I said I'm trying to swap to OpenGl anyway however it seems super complicated. Trying to take Jeff Buck's platform layer and the
Handmade Hero OpenGLDisplayBuffer function as a reference but it seems super complicated.
So will need some more time until it's working.

The main loop in Casey's game code normally calls Win32DisplayBufferInWindow which then calls SoftwareRenderCommands and OpenGLDisplayBitmap.

Do I need SoftwareRenderCommands or all the stuff that is happening in Win32DisplayBufferInWindow? Or is everything i need to render a bitmap located in OpenGlDisplayBitmap?

I really have no idea how OpenGL works.

None
Flyingsand
78 posts
#8449 Main game loop on OS X
2 years, 8 months ago

adge
Well thats weird. So why am I getting these low FPS then? I'm running it on a mid 2012 MBP retina. Maybe the machine is getting old but its still quite fast.


Well, it is possible that you're taking a small hit with your MBP, but I still don't believe that you wouldn't be able to hit at least 30fps with a 2012 MBP. I'm on a pretty beefy 2013 iMac, though.

adge

Are you using the terminal for compilation? If so can you show me the command?


No. As much as I hate Xcode, I just haven't found a setup I prefer on Mac.

adge

As I said I'm trying to swap to OpenGl anyway however it seems super complicated. Trying to take Jeff Buck's platform layer and the
Handmade Hero OpenGLDisplayBuffer function as a reference but it seems super complicated.
So will need some more time until it's working.

The main loop in Casey's game code normally calls Win32DisplayBufferInWindow which then calls SoftwareRenderCommands and OpenGLDisplayBitmap.

Do I need SoftwareRenderCommands or all the stuff that is happening in Win32DisplayBufferInWindow? Or is everything i need to render a bitmap located in OpenGlDisplayBitmap?

I really have no idea how OpenGL works.


If all you want to do is blit a 2D texture using OpenGL, it's not actually that bad on OS X. The GLEssentials sample project that you can find on Apple's developer site is pretty helpful in getting you set up with OpenGL. That's what I used as a guide, along with a few other tidbits from other sites. Bear in mind that my OpenGL knowledge is very minimal as well, but here is what I have for doing my bitmap blit. These are the relevant methods in my subclass of NSOpenGLView:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (void)prepareOpenGL {
    [super prepareOpenGL];
    
    _textureWidth = (GLsizei)self.bounds.size.width;
    _textureHeight = (GLsizei)self.bounds.size.height;
    _bitmapPixels = (uint32_t *)malloc(_textureWidth * _textureHeight * sizeof(uint32_t));
    memset(_bitmapPixels, 0, _textureWidth * _textureHeight * sizeof(uint32_t));
    
    glClearColor(0.f, 0.f, 0.f, 1.f);
    
    // Set pixel packing to byte-aligned for reading textures.
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    glGenTextures(1, &_bitmapTexture);
    glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _textureWidth, _textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
    GLenum glError = glGetError();
    NSAssert(glError == GL_NO_ERROR, @"OpenGl error loading texture: %d", glError);
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);
    
    // Enable vsync.
    GLint swapInt = 1;
    [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
    
    CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
    CVDisplayLinkSetOutputCallback(_displayLink, &DisplayCallback, (__bridge void *)self);
    
    CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
    CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
    CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat);
    
    CVDisplayLinkStart(_displayLink);
}


Then I have a -render method that is called each frame after all the drawing has completed into the texture buffer (i.e. at the end of the CVDisplayLink callback):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
- (void)render {
    [[self openGLContext] makeCurrentContext];
    
    CGLLockContext([[self openGLContext] CGLContextObj]);
    
    glClear(GL_COLOR_BUFFER_BIT);
    
    glBindTexture(GL_TEXTURE_2D, _bitmapTexture);
    glEnable(GL_TEXTURE_2D);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _textureWidth, _textureHeight, GL_RGBA, GL_UNSIGNED_BYTE, _bitmapPixels);
    glBegin(GL_QUADS);
        glTexCoord2f(0.f, 1.f); glVertex2f(-1.f, -1.f);
        glTexCoord2f(0.f, 0.f); glVertex2f(-1.f, 1.f);
        glTexCoord2f(1.f, 0.f); glVertex2f(1.f, 1.f);
        glTexCoord2f(1.f, 1.f); glVertex2f(1.f, -1.f);
    glEnd();
    
    CGLFlushDrawable([[self openGLContext] CGLContextObj]);
    CGLUnlockContext([[self openGLContext] CGLContextObj]);
}


So all that happens in -render is to bind the texture that was created in -prepareOpenGL, enable textures, do the texture mapping to the defined quad. Inside the glBegin/glEnd pairs, uv coordinates are associated with the quad's vertices (keeping in mind that the coordinate system in OS X has 0,0 in the bottom left).

Then you would just have to initialize your NSOpenGLView with the pixel format you want and set that as your window's content view. e.g.:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
NSOpenGLPixelFormatAttribute attrs[] =
{
    NSOpenGLPFAAccelerated,
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFADepthSize, 24,
    0
};

NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
NSCAssert(pixelFormat, @"No OpenGL pixel format");
FSOpenGLView *openGLView = [[FSOpenGLView alloc] initWithFrame:[window contentRectForFrameRect:windowRect] pixelFormat:pixelFormat];
window.contentView = openGLView;
adge
Adrian
46 posts

None

#8454 Main game loop on OS X
2 years, 8 months ago Edited by Adrian on Sept. 6, 2016, 3:36 p.m.

Hmmmm and yours is working? This is how I do it:

main.mm:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
global_variable NSOpenGLContext *GlobalGLContext;
global_variable GLuint OpenGLDefaultInternalTextureFormat;
global_variable GLuint TextureId;

int main(int argc, const char * argv[])
{

  //Setup OpenGL:
  NSOpenGLPixelFormatAttribute openGLAttributes[] =
  {
    NSOpenGLPFAAccelerated,
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFADepthSize, 24,
    0
  };

  NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes: openGLAttributes];
  GlobalGLContext = [[NSOpenGLContext alloc] initWithFormat: format shareContext:NULL];
  [format release];

  GLint swapInt = 1;
  [GlobalGLContext setValues: &swapInt forParameter: NSOpenGLCPSwapInterval];

  [GlobalGLContext setView: [window contentView]];

  [GlobalGLContext makeCurrentContext];

  //SetupOpenGL:
  glGenTextures(1, &TextureId);
  OpenGLDefaultInternalTextureFormat = GL_RGBA8;
  OpenGLDefaultInternalTextureFormat = 0x8C43; //GL_SRGB8_ALPHA8;
  glEnable(GL_FRAMEBUFFER_SRGB);
  

  while(running) 
  {
    [GlobalGLContext makeCurrentContext];

    ProcessEvents();
    RenderWeirdGradient(&GlobalBackbuffer, XOffset, YOffset);

    OpenGLDisplayBitmap(GlobalBackbuffer.Width, GlobalBackbuffer.Height,
                        GlobalBackbuffer.Memory, GlobalBackbuffer.Pitch,
                        WindowDimension.Width, WindowDimension.Height,
                        TextureId);

    [GlobalGLContext flushBuffer];
  }
}


This is the main part of my code. I don't have the View Delegate as it seems more "pure" to me. I got the idea from Jeff Bucks Minimal platform layer. I have an app delegate and a window delegate. The window gets created in the main function and the app and window delegates are set.
I only have one delegate which handles both application delegate and window delegate.

I think the OpenGL setup is done the right way. Though I just copied it also.

OpenGLDisplayBitmap:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
internal void OpenGLDisplayBitmap(int32 Width, int32 Height, void *Memory, int Pitch,
    int32 WindowWidth, int32 WindowHeight, GLuint BlitTexture)
{
    //Assert(Pitch == (Width*4));
    glViewport(0, 0, Width, Height);

    glDisable(GL_SCISSOR_TEST);
    glBindTexture(GL_TEXTURE_2D, BlitTexture);
    //
    // glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, Width, Height, 0,
    //              GL_BGRA_EXT, GL_UNSIGNED_BYTE, GlobalBackbuffer.Memory);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, &GlobalBackbuffer.Memory);


    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glEnable(GL_TEXTURE_2D);

    glClearColor(1.0f, 0.0f, 1.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();

    OpenGLSetScreenspace(Width, Height);

    // TODO(casey): Decide how we want to handle aspect ratio - black bars or crop?

    union v2 MinP = {0, 0};
    union v2 MaxP = {(r32)Width, (r32)Height};
    union v4 Color = {1, 1, 1, 1};

    OpenGLRectangle(MinP, MaxP, Color);

    glBindTexture(GL_TEXTURE_2D, 0);
}


This is the same function as Casey's handmade hero has implemented. However it doesn't work. Everything compiles but I just get a pink screen for whatever reason. Litte do I know.

There are two more functions that are invoked in OpenGLDisplayBitmap: OpenGLSetScreenspace, OpenGLRectangle. However I think that do nothing but here is the code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
inline void
OpenGLSetScreenspace(int32 Width, int32 Height)
{
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glMatrixMode(GL_PROJECTION);
    r32 a = SafeRatio1(2.0f, (r32)Width);
    r32 b = SafeRatio1(2.0f, (r32)Height);
    r32 Proj[] =
    {
         a,  0,  0,  0,
         0,  b,  0,  0,
         0,  0,  1,  0,
        -1, -1,  0,  1,
    };
    glLoadMatrixf(Proj);
}

inline void
OpenGLRectangle(union v2 MinP, union v2 MaxP, union v4 PremulColor, union v2 MinUV = V2(0, 0), v2 MaxUV = V2(1, 1))
{
    glBegin(GL_TRIANGLES);

    glColor4f(PremulColor.r, PremulColor.g, PremulColor.b, PremulColor.a);

    // NOTE(casey): Lower triangle
    glTexCoord2f(MinUV.x, MinUV.y);
    glVertex2f(MinP.x, MinP.y);

    glTexCoord2f(MaxUV.x, MinUV.y);
    glVertex2f(MaxP.x, MinP.y);

    glTexCoord2f(MaxUV.x, MaxUV.y);
    glVertex2f(MaxP.x, MaxP.y);

    // NOTE(casey): Upper triangle
    glTexCoord2f(MinUV.x, MinUV.y);
    glVertex2f(MinP.x, MinP.y);

    glTexCoord2f(MaxUV.x, MaxUV.y);
    glVertex2f(MaxP.x, MaxP.y);

    glTexCoord2f(MinUV.x, MaxUV.y);
    glVertex2f(MinP.x, MaxP.y);

    glEnd();
}


Yeah however it's not working. I'm currently going through Casey's OpenGL section. I hope it helps to learn and understand it a little bit and hopefully implement an own more simplified version of this shenanigans. But if you see what I'm doing wrong then please tell me.

I just don't want the drawing where apple thinks I should do it. So I don't like using views they so much force a design pattern on you. Is there a C Mac OS X API? I heard there was something like Carbon but its deprecated now and its 32 bit. I can't find any documentations. Does it still work?

And finally, whats that weird beast mode syntax?
1
2
3
inline void
OpenGLRectangle(union v2 MinP, union v2 MaxP, union v4 PremulColor, union v2 MinUV = V2(0, 0), v2 MaxUV = V2(1, 1))
{

Whats the political correct terminus to call this? I guess its C++?


One more thing: How fast will the Handmade Hero Engine/Game be when it's done? Like compared to stuff like Game Maker, Unity usw. Regarding 2D.

None