1#import <QuartzCore/QuartzCore.h>
2#import <OpenGLES/EAGLDrawable.h>
3
4#import "EAGLView.h"
5#import "allegroAppDelegate.h"
6#include <pthread.h>
7#include <float.h>
8
9#include "allegro5/allegro_iphone.h"
10#include "allegro5/internal/aintern_iphone.h"
11
12ALLEGRO_DEBUG_CHANNEL("iphone")
13
14typedef struct touch_t
15{
16   int      id;
17   UITouch* touch;
18} touch_t;
19
20/* Every UITouch have associated touch_t structure. This destructor
21 * is used in list which held touch information. While ending touch it will
22 * be called and memory will be freed.
23 */
24static void touch_item_dtor(void* value, void* userdata)
25{
26   (void)userdata;
27   al_free(value);
28}
29
30/* Search for touch_t associated with UITouch.
31 */
32static touch_t* find_touch(_AL_LIST* list, UITouch* nativeTouch)
33{
34   _AL_LIST_ITEM* item;
35
36   for (item = _al_list_front(list); item; item = _al_list_next(list, item)) {
37
38      touch_t* touch = (touch_t*)_al_list_item_data(item);
39
40      if (touch->touch == nativeTouch)
41         return touch;
42   }
43
44   return NULL;
45}
46
47@implementation EAGLView
48
49@synthesize context;
50@synthesize backingWidth;
51@synthesize backingHeight;
52
53
54// You must implement this method
55+ (Class)layerClass {
56    return [CAEAGLLayer class];
57}
58
59- (void)set_allegro_display:(ALLEGRO_DISPLAY *)display {
60   ALLEGRO_DISPLAY_IPHONE *d = (ALLEGRO_DISPLAY_IPHONE *)display;
61
62   allegro_display = display;
63
64   // Get the layer
65   CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
66
67   NSString *color_format = kEAGLColorFormatRGBA8;
68   if (display->extra_settings.settings[ALLEGRO_COLOR_SIZE] == 16)
69      color_format = kEAGLColorFormatRGB565;
70
71   eaglLayer.opaque = YES;
72   eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
73      [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
74      color_format, kEAGLDrawablePropertyColorFormat, nil];
75
76   if (display->flags & ALLEGRO_PROGRAMMABLE_PIPELINE) {
77      ALLEGRO_INFO("Attempting to create ES2 context\n");
78      context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
79      if (context == nil) {
80         ALLEGRO_WARN("ES2 context could not be created. Attempting to create ES1 context instead.\n");
81         display->flags &= ~ ALLEGRO_PROGRAMMABLE_PIPELINE;
82         context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
83      }
84   }
85   else {
86      ALLEGRO_INFO("Attempting to create ES1 context.\n");
87      context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
88   }
89
90   ALLEGRO_INFO("Context is %p\n", context);
91
92   if (!context || ![EAGLContext setCurrentContext:context]) {
93      ALLEGRO_ERROR("context is nil or setCurrentContext failed.\n");
94      [self release];
95      return;
96   }
97
98   /* FIXME: Make this depend on a display setting. */
99   [self setMultipleTouchEnabled:YES];
100
101
102   ALLEGRO_INFO("Created EAGLView.\n");
103}
104
105- (id)initWithFrame:(CGRect)frame {
106
107    ALLEGRO_DEBUG("Creating UIView.\n");
108
109    self = [super initWithFrame:frame];
110
111    touch_list = _al_list_create();
112
113    primary_touch = NULL;
114
115    touch_id_set       = [[NSMutableIndexSet alloc] init];
116    next_free_touch_id = 1;
117
118    return self;
119}
120
121- (void)make_current {
122   [EAGLContext setCurrentContext:context];
123   glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
124}
125
126- (void)reset_framebuffer {
127   glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
128}
129
130- (void)flip {
131    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
132    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
133}
134
135- (void)send_resize_event {
136   ALLEGRO_DISPLAY *display = allegro_display;
137
138   int x = self.frame.origin.x;
139   int y = self.frame.origin.y;
140   int w = self.frame.size.width;
141   int h = self.frame.size.height;
142
143   _al_event_source_lock(&display->es);
144   if (_al_event_source_needs_to_generate_event(&display->es)) {
145      ALLEGRO_EVENT event;
146      event.display.type = ALLEGRO_EVENT_DISPLAY_RESIZE;
147      event.display.timestamp = al_get_time();
148      event.display.x = x;
149      event.display.y = y;
150      event.display.width = w;
151      event.display.height = h;
152      event.display.orientation = _al_iphone_get_orientation(display);
153      _al_event_source_emit_event(&display->es, &event);
154   }
155   _al_event_source_unlock(&display->es);
156}
157
158- (void)layoutSubviews {
159    [EAGLContext setCurrentContext:context];
160    if (!viewRenderbuffer) {
161       [self createFramebuffer];
162       /* Depending on the orientation, the initial framebuffer dimensions may be
163        * rotated so we need to update them. For example
164        * a call to al_create_display(480, 640) will create a display of
165        * 640x480 pixels in landscape mode.
166        */
167       allegro_display->w = backingWidth;
168       allegro_display->h = backingHeight;
169    }
170    [self send_resize_event];
171}
172
173- (BOOL)orientation_supported:(UIInterfaceOrientation) o {
174   if (!allegro_display) return NO;
175   ALLEGRO_DISPLAY_IPHONE *d = (ALLEGRO_DISPLAY_IPHONE *)allegro_display;
176   if (d->extra->adapter != 0) return NO;
177   ALLEGRO_EXTRA_DISPLAY_SETTINGS *options = &allegro_display->extra_settings;
178   int supported = options->settings[ALLEGRO_SUPPORTED_ORIENTATIONS];
179   if (o == UIInterfaceOrientationPortrait) return supported & ALLEGRO_DISPLAY_ORIENTATION_0_DEGREES;
180   if (o == UIInterfaceOrientationLandscapeLeft) return supported & ALLEGRO_DISPLAY_ORIENTATION_90_DEGREES;
181   if (o == UIInterfaceOrientationPortraitUpsideDown) return supported & ALLEGRO_DISPLAY_ORIENTATION_180_DEGREES;
182   if (o == UIInterfaceOrientationLandscapeRight) return supported & ALLEGRO_DISPLAY_ORIENTATION_270_DEGREES;
183   return NO;
184}
185
186- (BOOL)createFramebuffer {
187   ALLEGRO_DISPLAY_IPHONE *d = (ALLEGRO_DISPLAY_IPHONE *)allegro_display;
188
189    if (d->extra->adapter == 0 && [self respondsToSelector:@selector(contentScaleFactor)]) {
190        scale = self.contentScaleFactor = [[UIScreen mainScreen] scale];
191        ALLEGRO_INFO("Screen scale is %f\n", self.contentScaleFactor);
192    }
193    else {
194    	scale = 1.0f;
195    }
196
197    glGenFramebuffersOES(1, &viewFramebuffer);
198    glGenRenderbuffersOES(1, &viewRenderbuffer);
199
200    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
201    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
202    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
203    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
204
205    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
206    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
207
208    ALLEGRO_INFO("Creating GL framebuffer %dx%d.\n", backingWidth, backingHeight);
209
210    if (allegro_display->extra_settings.settings[ALLEGRO_DEPTH_SIZE]) {
211        GLint depth_stencil_format;
212        if (allegro_display->extra_settings.settings[ALLEGRO_STENCIL_SIZE]) {
213	    depth_stencil_format = GL_DEPTH24_STENCIL8_OES;
214	}
215	else {
216	    depth_stencil_format = GL_DEPTH_COMPONENT16_OES;
217	}
218        glGenRenderbuffersOES(1, &depthRenderbuffer);
219        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
220        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, depth_stencil_format, backingWidth, backingHeight);
221        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
222        if (allegro_display->extra_settings.settings[ALLEGRO_STENCIL_SIZE]) {
223        	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_STENCIL_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
224	}
225    }
226
227    if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
228        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
229        return NO;
230    }
231
232    return YES;
233}
234
235
236- (void)destroyFramebuffer {
237
238    glDeleteFramebuffersOES(1, &viewFramebuffer);
239    viewFramebuffer = 0;
240    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
241    viewRenderbuffer = 0;
242
243    if (depthRenderbuffer) {
244        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
245        depthRenderbuffer = 0;
246    }
247}
248
249- (void)dealloc {
250    if (touch_list)
251      _al_list_destroy(touch_list);
252
253    [touch_id_set release];
254
255    if ([EAGLContext currentContext] == context) {
256        [EAGLContext setCurrentContext:nil];
257    }
258
259    [context release];
260    [super dealloc];
261}
262
263/* Handling of touch events. */
264
265-(NSArray*)getSortedTouches:(NSSet*)touches
266{
267   NSArray* unsorted = [NSArray arrayWithArray: [touches allObjects]];
268   NSArray* sorted   = [unsorted sortedArrayUsingComparator: ^(id obj1, id obj2)
269   {
270     if ([obj1 timestamp] > [obj2 timestamp])
271       return (NSComparisonResult)NSOrderedDescending;
272     else if ([obj1 timestamp] < [obj2 timestamp])
273       return (NSComparisonResult)NSOrderedAscending;
274     else
275       return (NSComparisonResult)NSOrderedSame;
276   }];
277   return sorted;
278}
279
280// Handles the start of a touch
281-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
282{
283   (void)event;
284
285   // TODO: handle double-clicks (send two events?)
286   // NSUInteger numTaps = [[touches anyObject] tapCount];
287   // Enumerate through all the touch objects.
288
289   for (UITouch *nativeTouch in touches) {
290      /* Create new touch_t and associate ID with UITouch. */
291      touch_t* touch = al_malloc(sizeof(touch_t));
292
293      touch->touch = nativeTouch;
294
295      if ([touch_id_set count] != 0) {
296         touch->id = [touch_id_set firstIndex];
297         [touch_id_set removeIndex:touch->id];
298      }
299      else
300         touch->id = next_free_touch_id++;
301
302      _al_list_push_back_ex(touch_list, touch, touch_item_dtor);
303
304      CGPoint p = [nativeTouch locationInView:[nativeTouch view]];
305
306      if (NULL == primary_touch)
307         primary_touch = nativeTouch;
308
309      _al_iphone_touch_input_handle_begin(touch->id, al_get_time(),
310      p.x*scale, p.y*scale, primary_touch == nativeTouch, allegro_display);
311   }
312}
313
314// Handles the continuation of a touch.
315-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
316{
317   (void)event;
318
319   touch_t* touch;
320
321   // Enumerates through all touch objects
322   for (UITouch *nativeTouch in touches) {
323      if ((touch = find_touch(touch_list, nativeTouch))) {
324
325         CGPoint p = [nativeTouch locationInView:[nativeTouch view]];
326
327         _al_iphone_touch_input_handle_move(touch->id, al_get_time(),
328         p.x*scale, p.y*scale, primary_touch == nativeTouch, allegro_display);
329      }
330   }
331}
332
333// Handles the end of a touch event.
334-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
335{
336   (void)event;
337
338   touch_t* touch;
339
340   // Enumerates through all touch objects
341   for (UITouch *nativeTouch in touches) {
342      if ((touch = find_touch(touch_list, nativeTouch))) {
343
344         CGPoint p = [nativeTouch locationInView:[nativeTouch view]];
345
346         _al_iphone_touch_input_handle_end(touch->id, al_get_time(),
347            p.x*scale, p.y*scale, primary_touch == nativeTouch, allegro_display);
348
349         [touch_id_set addIndex:touch->id];
350         _al_list_remove(touch_list, touch);
351
352         if (primary_touch == nativeTouch)
353            primary_touch = NULL;
354      }
355   }
356}
357
358// Quoting Apple docs:
359// "The system cancelled tracking for the touch, as when (for example) the user
360// puts the device to his or her face."
361-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
362{
363   (void)event;
364
365   touch_t* touch;
366
367   // Enumerates through all touch objects
368   for (UITouch *nativeTouch in touches) {
369      if ((touch = find_touch(touch_list, nativeTouch))) {
370
371         CGPoint p = [nativeTouch locationInView:[nativeTouch view]];
372
373         _al_iphone_touch_input_handle_cancel(touch->id, al_get_time(),
374         p.x*scale, p.y*scale, primary_touch == nativeTouch, allegro_display);
375
376         if (primary_touch == nativeTouch)
377         primary_touch = NULL;
378
379         [touch_id_set addIndex:touch->id];
380         _al_list_remove(touch_list, touch);
381      }
382   }
383}
384
385-(BOOL)canBecomeFirstResponder {
386    return YES;
387}
388
389-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
390{
391    (void)motion;
392}
393
394@end
395
396