#ifdef QB64_MACOSX
#include <sys/sysctl.h>
#endif

// trigger recompilation = 3

int32 displayorder_screen=1;
int32 displayorder_hardware=2;
int32 displayorder_glrender=3;
int32 displayorder_hardware1=4;

//sub__displayorder( 1 , 2 , 4 , 3 );
//id.specialformat = "[{_SCREEN|_HARDWARE|_HARDWARE1|_GLRENDER}[,{_SCREEN|_HARDWARE|_HARDWARE1|_GLRENDER}[,{_SCREEN|_HARDWARE|_HARDWARE1|_GLRENDER}[,{_SCREEN|_HARDWARE|_HARDWARE1|_GLRENDER}]]]]"
void sub__displayorder(int32 method1,int32 method2,int32 method3,int32 method4){
    
    //check no value has been used twice
    if (method1!=0) if (method1==method2||method1==method3||method1==method4){error(5); return;}
    if (method2!=0) if (method2==method1||method2==method3||method2==method4){error(5); return;}
    if (method3!=0) if (method3==method1||method3==method2||method3==method4){error(5); return;}
    if (method4!=0) if (method4==method1||method4==method2||method4==method3){error(5); return;}
    displayorder_screen=0;
    displayorder_hardware=0;
    displayorder_hardware1=0;
    displayorder_glrender=0;
    static int32 i,method;
    for (i=1;i<=4;i++){
        if (i==1) method=method1;
        if (i==2) method=method2;
        if (i==3) method=method3;
        if (i==4) method=method4;
        if (method==1) displayorder_screen=i;
        if (method==2) displayorder_hardware=i;
        if (method==3) displayorder_hardware1=i;
        if (method==4) displayorder_glrender=i;
    }
}

//int32 gl_render_method=2; //1=behind, 2=ontop[default], 3=only
void sub__glrender(int32 method){
    //gl_render_method=method;
    if (method==1) sub__displayorder(4,1,2,3);
    if (method==2) sub__displayorder(1,2,4,3);
    if (method==3) sub__displayorder(4,0,0,0);
}


#ifndef QB64_GUI //begin stubs
    
    
    
    
    #else //end stubs
    
    
    
    
    
    void GLUT_RESHAPE_FUNC(int width, int height){
        resize_event_x=width; resize_event_y=height;
        resize_event=-1;
        display_x_prev=display_x,display_y_prev=display_y;
        display_x=width; display_y=height;
        resize_pending=0;
        os_resize_event=1;
        set_view(VIEW_MODE__UNKNOWN);
        //***glutReshapeWindow(...) has no effect if called
        //   within GLUT_RESHAPE_FUNC***
    }
    
    
    
    
    //displaycall is the window of time to update our display
    
    
    #ifdef DEPENDENCY_GL
        extern void SUB__GL();
    #endif
    
    
    
    
    
    
    
    
    
    #define GL_BGR 0x80E0
    #define GL_BGRA 0x80E1
    
    
    
    /* reference
        struct hardware_img_struct{
        int32 w;
        int32 h;
        int32 texture_handle;
        int32 dest_context_handle;//used when rendering other images onto this image
        int32 temp;//if =1, delete immediately after use
        }
        list *hardware_img_handles=NULL;
    */
    
    
    
    void free_hardware_img(int32 handle, int32 caller_id){
        
        hardware_img_struct* hardware_img;
        hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,handle);
        
        if (hardware_img==NULL){
            alert("free_hardware_img: image does not exist");
        }
        
        if (hardware_img->dest_context_handle){
            GLuint context=(GLuint)hardware_img->dest_context_handle;
            glDeleteFramebuffersEXT(1, &context);
        }
        if (hardware_img->depthbuffer_handle){
            GLuint depthbuffer_handle=(GLuint)hardware_img->depthbuffer_handle;
            glDeleteFramebuffersEXT(1, &depthbuffer_handle);
        }
        GLuint texture=(GLuint)hardware_img->texture_handle;
        glDeleteTextures(1, &texture);
        
        //test reasset of hardware+img
        //hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,handle);
        //if (hardware_img==NULL) alert("free_hardware_img: image does not exist");
        
        //if image has not been used, it may still have buffered pixel content
        
        if (hardware_img->software_pixel_buffer!=NULL){
            free(hardware_img->software_pixel_buffer);
        }
        
        list_remove(hardware_img_handles,handle);
        
    }
    
    
    /*
        int32 new_hardware_frame(int32 x, int32 y){
        int32 handle=new_hardware_frame_handle();
        glBindTexture (GL_TEXTURE_2D, handle);
        glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
        return handle;
        }
        
        void free_hardware_frame(int32 handle){
        GLuint texture=(GLuint)handle;
        glDeleteTextures(1, &texture);
        }
    */
    
    
    
    
    void prepare_environment_2d(){//called prior to rendering 2D content
        
        //precalculate critical dimensions, offsets & ratios
        
        static int32 can_scale;//can the screen be scaled on the window
        can_scale=0;
        static int32 need_square_pixels;//do we need square_pixels? if not we can stretch the screen
        need_square_pixels=0;
        
        environment_2d__screen_smooth=0;
        
        environment_2d__letterbox=0;
        
        if (full_screen>0){//in full screen
            //reference: ---int32 full_screen_set=-1;//0(windowed),1(stretched/closest),2(1:1)---
            can_scale=1;
            if (full_screen==2) need_square_pixels=1;
            //note: 'letter-boxing' is only requred where the size of the window cannot be controlled, and the only place where this is the
            //      case is full screen mode _SQUAREPIXELS
            environment_2d__screen_smooth=fullscreen_smooth;
            }else{//windowed
            if (resize_auto>0){//1=STRETCH,2=SMOOTH
                can_scale=1;
                if (resize_auto==2) environment_2d__screen_smooth=1;
                //note: screen will fix its aspect ratio automatically, so there is no need to enforce squarepixels
            }
        }
        
        if (environment_2d__screen_width==environment__window_width &&
        environment_2d__screen_height==environment__window_height){
            //screen size matches window
            environment_2d__screen_x1=0;
            environment_2d__screen_y1=0;
            environment_2d__screen_x2=environment_2d__screen_width-1;
            environment_2d__screen_y2=environment_2d__screen_height-1;
            environment_2d__screen_x_scale=1.0f;
            environment_2d__screen_y_scale=1.0f;
            environment_2d__screen_scaled_width=environment_2d__screen_width;
            environment_2d__screen_scaled_height=environment_2d__screen_height;
            environment_2d__screen_smooth=0;//no smoothing required
            }else{
            //screen size does not match
            //calculate ratios
            static float window_ratio;
            static float screen_ratio;
            window_ratio=(float)environment__window_width/(float)environment__window_height;
            screen_ratio=(float)environment_2d__screen_width/(float)environment_2d__screen_height;
            if (can_scale==0){
                //screen will appear in the top-left of the window with blank space on the bottom & right
                environment_2d__screen_x1=0;
                environment_2d__screen_y1=0;
                environment_2d__screen_x2=environment_2d__screen_width-1;
                environment_2d__screen_y2=environment_2d__screen_height-1;
                goto cant_scale;
            }
            if (need_square_pixels==0||(window_ratio==screen_ratio)){
                //can stretch, no 'letter-box' required
                environment_2d__screen_x1=0;
                environment_2d__screen_y1=0;
                environment_2d__screen_x2=environment__window_width-1;
                environment_2d__screen_y2=environment__window_height-1;
                }else{
                //'letter-box' required
                //this section needs revision
                static float x_scale,y_scale;
                static int32 x1,y1,x2,y2,z,x_limit,y_limit,x_offset,y_offset;
                //x_scale=(float)environment_2d__screen_width/(float)environment__window_width;
                //y_scale=(float)environment_2d__screen_height/(float)environment__window_height;
                //x_offset=0; y_offset=0;
                
                x1=0; y1=0; x2=environment__window_width-1; y2=environment__window_height-1;
                //x_limit=x2; y_limit=y2;
                if (window_ratio>screen_ratio){
                    //pad sides
                    z=(float)environment__window_height*screen_ratio;//new width
                    x1=environment__window_width/2-z/2;
                    x2=x1+z-1;
                    environment_2d__letterbox=1;//vertical black stripes required
                    //x_offset=-x1; x_scale=(float)environment_2d__screen_width/(float)z; x_limit=z-1;
                    }else{
                    //pad top/bottom
                    z=(float)environment__window_width/screen_ratio;//new height
                    y1=environment__window_height/2-z/2;
                    y2=y1+z-1;
                    environment_2d__letterbox=2;//horizontal black stripes required
                    //y_offset=-y1; y_scale=(float)environment_2d__screen_height/(float)z; y_limit=z-1;
                }
                environment_2d__screen_x1=x1;
                environment_2d__screen_y1=y1;
                environment_2d__screen_x2=x2;
                environment_2d__screen_y2=y2;
            }
            cant_scale:
            environment_2d__screen_scaled_width=environment_2d__screen_x2-environment_2d__screen_x1+1;
            environment_2d__screen_scaled_height=environment_2d__screen_y2-environment_2d__screen_y1+1;
            environment_2d__screen_x_scale=(float)environment_2d__screen_scaled_width/(float)environment_2d__screen_width;
            environment_2d__screen_y_scale=(float)environment_2d__screen_scaled_height/(float)environment_2d__screen_height;
        }
        
    }//prepare_environment_2d
    
    
    int32 environment_2d__get_window_x1_coord(int32 x){
        return qbr_float_to_long(((float)x)*environment_2d__screen_x_scale)+environment_2d__screen_x1;
    }
    int32 environment_2d__get_window_y1_coord(int32 y){
        return qbr_float_to_long((float)y*environment_2d__screen_y_scale)+environment_2d__screen_y1;
    }
    int32 environment_2d__get_window_x2_coord(int32 x){
        return qbr_float_to_long(((float)x+1.0f)*environment_2d__screen_x_scale-1.0f)+environment_2d__screen_x1;
    }
    
    int32 environment_2d__get_window_y2_coord(int32 y){
        return qbr_float_to_long(((float)y+1.0f)*environment_2d__screen_y_scale-1.0f)+environment_2d__screen_y1;
    }
    
    struct environment_2d__window_rect_struct{
        int32 x1;
        int32 y1;
        int32 x2;
        int32 y2;
    };
    
    //this functions returns a constant rect dimensions to stop warping of image
    environment_2d__window_rect_struct tmp_rect;
    environment_2d__window_rect_struct *environment_2d__screen_to_window_rect(int32 x1,int32 y1,int32 x2,int32 y2){
        tmp_rect.x1=qbr_float_to_long(((float)x1)*environment_2d__screen_x_scale)+environment_2d__screen_x1;
        tmp_rect.y1=qbr_float_to_long(((float)y1)*environment_2d__screen_y_scale)+environment_2d__screen_y1;
        static int32 w,h;
        w=abs(x2-x1)+1; h=abs(y2-y1)+1;
        //force round upwards to correct gaps when tiling
        w=((float)w)*environment_2d__screen_x_scale+0.99f;
        h=((float)h)*environment_2d__screen_y_scale+0.99f;
        tmp_rect.x2=w-1+tmp_rect.x1;
        tmp_rect.y2=h-1+tmp_rect.y1;
        //(code which doesn't support tiling)
        //tmp_rect.x2=qbr_float_to_long(((float)w)*environment_2d__screen_x_scale-1.0f)+tmp_rect.x1;
        //tmp_rect.y2=qbr_float_to_long(((float)h)*environment_2d__screen_y_scale-1.0f)+tmp_rect.y1;
        return &tmp_rect;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    float *hardware_buffer_vertices=(float*)malloc(sizeof(float)*1);
    int32 hardware_buffer_vertices_max=1;
    int32 hardware_buffer_vertices_count=0;
    
    float *hardware_buffer_texcoords=(float*)malloc(sizeof(float)*1);
    int32 hardware_buffer_texcoords_max=1;
    int32 hardware_buffer_texcoords_count=0;
    
    void hardware_buffer_flush(){
        if (hardware_buffer_vertices_count){
            //ref: http://stackoverflow.com/questions/5009014/draw-square-with-opengl-es-for-ios
            if (hardware_buffer_vertices_count==hardware_buffer_texcoords_count){
                glVertexPointer(2, GL_FLOAT, 2*sizeof(GL_FLOAT), hardware_buffer_vertices); //http://www.opengl.org/sdk/docs/man2/xhtml/glVertexPointer.xml
                glTexCoordPointer(2, GL_FLOAT, 2*sizeof(GL_FLOAT), hardware_buffer_texcoords); //http://www.opengl.org/sdk/docs/man2/xhtml/glTexCoordPointer.xml
                glDrawArrays(GL_TRIANGLES, 0, hardware_buffer_vertices_count/2);//start index, number of indexes
                }else{
                glVertexPointer(3, GL_FLOAT, 3*sizeof(GL_FLOAT), hardware_buffer_vertices); //http://www.opengl.org/sdk/docs/man2/xhtml/glVertexPointer.xml
                glTexCoordPointer(2, GL_FLOAT, 2*sizeof(GL_FLOAT), hardware_buffer_texcoords); //http://www.opengl.org/sdk/docs/man2/xhtml/glTexCoordPointer.xml
                glDrawArrays(GL_TRIANGLES, 0, hardware_buffer_vertices_count/3);//start index, number of indexes
            }
            hardware_buffer_vertices_count=0;
            hardware_buffer_texcoords_count=0;
        }
    }
    
    
    
    
    void set_smooth(int32 new_mode_shrunk,int32 new_mode_stretched){
        static int32 current_mode_shrunk;
        current_mode_shrunk=render_state.source->smooth_shrunk;
        static int32 current_mode_stretched;
        current_mode_stretched=render_state.source->smooth_stretched;
        if (new_mode_shrunk==current_mode_shrunk&&new_mode_stretched==current_mode_stretched) return;
        hardware_buffer_flush();
        if (new_mode_shrunk==SMOOTH_MODE__DONT_SMOOTH){
            if (render_state.source->PO2_fix==PO2_FIX__MIPMAPPED){
                glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                }else{
                glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//Use _MAPTRIANGLE's _SMOOTHSHRUNK to apply linear filtering here
            }
        }
        if (new_mode_shrunk==SMOOTH_MODE__SMOOTH){
            glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        }
        if (new_mode_stretched==SMOOTH_MODE__DONT_SMOOTH){
            if (render_state.source->PO2_fix==PO2_FIX__MIPMAPPED){
                glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                }else{
                glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            }
        }
        if (new_mode_stretched==SMOOTH_MODE__SMOOTH){
            glTexParameterf ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        }
        render_state.source->smooth_shrunk=new_mode_shrunk;
        render_state.source->smooth_stretched=new_mode_stretched;
    }
    
    void set_texture_wrap(int32 new_mode){
        static int32 current_mode;
        current_mode=render_state.source->texture_wrap;
        if (new_mode==current_mode) return;
        hardware_buffer_flush();
        if (new_mode==TEXTURE_WRAP_MODE__DONT_WRAP){
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        }
        if (new_mode==TEXTURE_WRAP_MODE__WRAP){
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        }
        render_state.source->texture_wrap=new_mode;
    }
    
    void set_alpha(int32 new_mode){
        static int32 current_mode;
        current_mode=render_state.use_alpha;
        if (new_mode==current_mode) return;
        hardware_buffer_flush();
        if (new_mode==ALPHA_MODE__DONT_BLEND){
            glDisable(GL_BLEND);
        }
        if (new_mode==ALPHA_MODE__BLEND){
            glEnable(GL_BLEND);
            if (framebufferobjects_supported){
                #ifndef QB64_GLES
                    //glBlendFuncSeparateEXT(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
                    glBlendFuncSeparateEXT(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
                    #else
                    glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE);
                #endif
                }else{
                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            }
        }
        render_state.use_alpha=new_mode;
    }
    
    void set_depthbuffer(int32 new_mode){
        
        static int32 current_mode;
        current_mode=render_state.depthbuffer_mode;
        if (new_mode==current_mode) return;
        hardware_buffer_flush();
        if (new_mode==DEPTHBUFFER_MODE__OFF){
            glDisable(GL_DEPTH_TEST);
            glAlphaFunc(GL_ALWAYS, 0);
        }
        if (new_mode==DEPTHBUFFER_MODE__ON){
            glEnable(GL_DEPTH_TEST);
            glDepthMask(GL_TRUE);
            glAlphaFunc(GL_GREATER, 0.001);
            glEnable(GL_ALPHA_TEST);
        }
        if (new_mode==DEPTHBUFFER_MODE__LOCKED){
            glEnable(GL_DEPTH_TEST);
            glDepthMask(GL_FALSE);
            glAlphaFunc(GL_ALWAYS, 0);
        }
        render_state.depthbuffer_mode=new_mode;
    }
    
    void set_cull_mode(int32 new_mode){
        static int32 current_mode;
        current_mode=render_state.cull_mode;
        if (new_mode==current_mode) return;
        hardware_buffer_flush();
        if (new_mode==CULL_MODE__NONE){
            glDisable(GL_CULL_FACE);
        }
        if (new_mode==CULL_MODE__CLOCKWISE_ONLY){
            glFrontFace(GL_CW);
            if (current_mode!=CULL_MODE__ANTICLOCKWISE_ONLY) glEnable(GL_CULL_FACE);
        }
        if (new_mode==CULL_MODE__ANTICLOCKWISE_ONLY){
            glFrontFace(GL_CCW);
            if (current_mode!=CULL_MODE__CLOCKWISE_ONLY) glEnable(GL_CULL_FACE);
        }
        render_state.cull_mode=new_mode;
    }
    
    void set_view(int32 new_mode){ //set view can only be called after the correct destination is chosen
        static int32 current_mode;
        current_mode=render_state.view_mode;
        if (new_mode==current_mode) return;
        hardware_buffer_flush();
        if (new_mode==VIEW_MODE__RESET){
            glDisable(GL_TEXTURE_2D);
            glDisable(GL_ALPHA_TEST);
            glDisable(GL_BLEND);
            glDisable(GL_COLOR_MATERIAL);
            glDisable(GL_DEPTH_TEST);
            glDepthMask(GL_TRUE);
            glDisable(GL_LIGHTING);
            glFrontFace(GL_CCW);
            glCullFace(GL_BACK);
            glDisable(GL_CULL_FACE);
            glDisableClientState(GL_VERTEX_ARRAY);
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
            glAlphaFunc(GL_ALWAYS, 0);
            if (framebufferobjects_supported) glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
            glBindTexture (GL_TEXTURE_2D, 0);
            glClear(GL_DEPTH_BUFFER_BIT);
            glColor4f(1.f, 1.f, 1.f, 1.f);
            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            //invalidate current states
            set_alpha(ALPHA_MODE__UNKNOWN);
            set_depthbuffer(DEPTHBUFFER_MODE__UNKNOWN);
            set_cull_mode(CULL_MODE__UNKNOWN);
            set_render_source(INVALID_HARDWARE_HANDLE);
            set_render_dest(INVALID_HARDWARE_HANDLE);
            new_mode=VIEW_MODE__UNKNOWN;//resets are performed before unknown operations are executed
        }
        if (new_mode==VIEW_MODE__2D){
            if (current_mode!=VIEW_MODE__3D){
                glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
                glDisable(GL_COLOR_MATERIAL);
                glDisable(GL_LIGHTING);
                set_alpha(ALPHA_MODE__BLEND);
                glEnable(GL_TEXTURE_2D);
                glEnableClientState(GL_VERTEX_ARRAY);
                glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                glCullFace(GL_BACK);
            }
            
            
            
            
            if (render_state.dest_handle==0){
                static int32 dst_w,dst_h;
                static int32 scale_factor=0;

                #ifdef QB64_MACOSX
                    if (scale_factor==0) {
                        // by default scale_factor should be 1, but in macOS Catalina (10.15.*) scale_factor must be setted in 2
                        // * in cases where the app is executed on system with Retina Display
                        scale_factor = 1; // by default

                        // lookup for retina/5k output from system_profiler (storing all outpun in stream)
                        bool b_isRetina, b_is5k;
                        FILE* consoleStream = popen("system_profiler SPDisplaysDataType", "r");
                        if (consoleStream) {
                            char buffer[128];
                            while (!feof(consoleStream)) {
                                if (fgets(buffer, 128, consoleStream) != NULL) {
                                    string szBuffer(buffer);
                                    
                                    if (!b_isRetina) b_isRetina = (szBuffer.rfind("Retina") != ULONG_MAX);
                                    if (!b_is5k) b_is5k = (szBuffer.rfind("5K") != ULONG_MAX);
                                }
                            }
                        }
                        pclose(consoleStream);

                        if (b_isRetina || b_is5k) {
                            // apply only factor = 2 if macOS is Catalina (11.15.* // kern.osrelease 19.*)
                            char str[256];
                            size_t size = sizeof(str);
                            int ret = sysctlbyname("kern.osrelease", str, &size, NULL, 0);
                          
                            string sz_osrelease(str);
                            if (sz_osrelease.rfind("19.") == 0) scale_factor=2;
                        }
                    }
                #else
                    scale_factor=1;
                #endif

                dst_w=environment__window_width;
                dst_h=environment__window_height;
                
                //alert(dst_w);
                //alert(dst_h);
                
                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                glOrtho(0.0, dst_w, 0.0, dst_h, -1.0, 1.0);
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                glScalef(1, -1, 1);//flip vertically
                glTranslatef(0, -dst_h, 0);//move to new vertical position
                glViewport(0,0,dst_w * scale_factor,dst_h * scale_factor);
                
                
                }else{
                static hardware_img_struct* hardware_img;
                hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,render_state.dest_handle);
                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                gluOrtho2D(0, hardware_img->w, 0, hardware_img->h);
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                glViewport(0,0,hardware_img->w,hardware_img->h);
            }
        }
        if (new_mode==VIEW_MODE__3D){
            if (current_mode!=VIEW_MODE__2D){
                glColor4f(1.f, 1.f, 1.f, 1.f);
                glDisable(GL_COLOR_MATERIAL);
                glDisable(GL_LIGHTING);
                set_alpha(ALPHA_MODE__BLEND);
                glEnable(GL_TEXTURE_2D);
                glEnableClientState(GL_VERTEX_ARRAY);
                glEnableClientState(GL_TEXTURE_COORD_ARRAY);
                glCullFace(GL_BACK);
            }
            if (render_state.dest_handle==0){
                static int32 dst_w,dst_h;
                dst_w=environment__window_width;
                dst_h=environment__window_height;
                glViewport(0, 0, (GLsizei)dst_w, (GLsizei)dst_h);
                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                
                //note: the max FOV is 90-degrees (this maximum applies to the longest screen dimension)
                float fov;
                if (environment_2d__screen_scaled_width>environment_2d__screen_scaled_height){
                    fov=90.0f*((float)environment__window_width/(float)environment_2d__screen_scaled_width);
                    //convert fov from horizontal to vertical
                    fov=fov*((float)dst_h/(float)dst_w);
                    }else{
                    fov=90.0f*((float)environment__window_height/(float)environment_2d__screen_scaled_height);
                }
                gluPerspective(fov, (GLfloat)dst_w / (GLfloat)dst_h, 0.1, 10000.0); // Set the Field of view angle (in degrees), the aspect ratio of our window, and the new and far planes
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                }else{
                
                static hardware_img_struct* hardware_img;
                hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,render_state.dest_handle);
                
                static int32 dst_w,dst_h;
                dst_w=hardware_img->w;
                dst_h=hardware_img->h;
                glViewport(0, 0, (GLsizei)dst_w, (GLsizei)dst_h);
                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                glScalef (1.0, -1.0, 1.0);
                //note: the max FOV is 90-degrees (this maximum applies to the longest screen dimension)
                float fov;
                if (dst_w>dst_h){
                    fov=90.0f;
                    //convert fov from horizontal to vertical
                    fov=fov*((float)dst_h/(float)dst_w);
                    }else{
                    fov=90.0f;
                }
                gluPerspective(fov, (GLfloat)dst_w / (GLfloat)dst_h, 0.1, 10000.0); // Set the Field of view angle (in degrees), the aspect ratio of our window, and the new and far planes
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                //alert("3D rendering onto FBO not supported yet");
            }
        }
        render_state.view_mode=new_mode;
    }//change_render_state
    
    
    void set_render_source(int32 new_handle){
        if (new_handle==INVALID_HARDWARE_HANDLE){
            hardware_buffer_flush();
            render_state.source_handle=INVALID_HARDWARE_HANDLE;
            return;
        }
        int32 current_handle;
        current_handle=render_state.source_handle;
        
        if (current_handle==new_handle) return;
        
        hardware_buffer_flush();
        
        hardware_img_struct* hardware_img;
        hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,new_handle);
        if (hardware_img->texture_handle==0) hardware_img_buffer_to_texture(new_handle);
        glBindTexture (GL_TEXTURE_2D, hardware_img->texture_handle);
        render_state.source_handle=new_handle;
        render_state.source=&hardware_img->source_state;
        
        //note: some older systems require calling glTexParameterf after textures are rebound
        if (framebufferobjects_supported==0){
            render_state.source->smooth_shrunk=SMOOTH_MODE__UNKNOWN;
            render_state.source->smooth_stretched=SMOOTH_MODE__UNKNOWN;
        }
        
    }
    
    void set_render_dest(int32 new_handle){
        if (new_handle==INVALID_HARDWARE_HANDLE){
            hardware_buffer_flush();
            render_state.dest_handle=INVALID_HARDWARE_HANDLE;
            set_view(VIEW_MODE__UNKNOWN);
            return;
        }
        //0=primary surface
        static int32 current_handle;
        current_handle=render_state.dest_handle;
        if (new_handle==current_handle) return;
        hardware_buffer_flush();
        set_view(VIEW_MODE__UNKNOWN);
        if (new_handle==0){
            if (framebufferobjects_supported) glBindFramebufferEXT(GL_FRAMEBUFFER, 0);
            render_state.dest=&dest_render_state0;
            }else{
            static hardware_img_struct* hardware_img;
            hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,new_handle);
            //convert to regular texture first if necessary
            if (hardware_img->texture_handle==0) hardware_img_buffer_to_texture(new_handle);
            //does it have a dest context/FBO? if not create one
            if (hardware_img->dest_context_handle==0){
                
                static GLuint framebuffer_handle;
                framebuffer_handle=0;
                glGenFramebuffersEXT(1, &framebuffer_handle);
                glBindFramebufferEXT(GL_FRAMEBUFFER, framebuffer_handle);
                hardware_img->dest_context_handle=framebuffer_handle;
                glFramebufferTexture2DEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, hardware_img->texture_handle, 0);
                
                //glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
                //glClear(GL_COLOR_BUFFER_BIT);
                glColor4f(1.f, 1.f, 1.f, 1.f);
                
                set_render_source(INVALID_HARDWARE_HANDLE);
                
                }else{
                glBindFramebufferEXT(GL_FRAMEBUFFER, hardware_img->dest_context_handle);
            }
            render_state.dest=&hardware_img->dest_state;
        }
        render_state.dest_handle=new_handle;
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    void hardware_img_put(int32 dst_x1,int32 dst_y1,int32 dst_x2,int32 dst_y2,
    int32 src_img,int32 dst_img,
    int32 src_x1,int32 src_y1,int32 src_x2,int32 src_y2,
    int32 use_alpha,
    int32 smooth
    ){
        
        if (dst_img<0) dst_img=0;//both layers render to the primary context
        
        //ensure dst_x1/y1 represent top-left co-ordinate of destination
        static int32 swap_tmp;
        if (dst_x2<dst_x1){
            swap_tmp=dst_x2; dst_x2=dst_x1; dst_x1=swap_tmp;
            swap_tmp=src_x2; src_x2=src_x1; src_x1=swap_tmp;
        }
        if (dst_y2<dst_y1){
            swap_tmp=dst_y2; dst_y2=dst_y1; dst_y1=swap_tmp;
            swap_tmp=src_y2; src_y2=src_y1; src_y1=swap_tmp;
        }
        
        set_render_dest(dst_img);
        
        
        set_view(VIEW_MODE__2D);
        
        
        
        if (dst_img){
            //static hardware_img_struct* dst_hardware_img;
            //dst_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,dst_img);
            //(no specific action required here --area reserved for future use)
            }else{ //dest is 0
            environment_2d__window_rect_struct *rect;
            rect=environment_2d__screen_to_window_rect(dst_x1,dst_y1,dst_x2,dst_y2);
            dst_x1=rect->x1;
            dst_y1=rect->y1;
            dst_x2=rect->x2;
            dst_y2=rect->y2;
        }
        
        set_render_source(src_img);
        
        static hardware_img_struct* src_hardware_img;
        static int32 src_h,src_w;
        src_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,src_img);
        src_h=src_hardware_img->h;
        src_w=src_hardware_img->w;
        
        
        
        if (smooth){
            set_smooth(SMOOTH_MODE__SMOOTH,SMOOTH_MODE__SMOOTH);
            }else{
            set_smooth(SMOOTH_MODE__DONT_SMOOTH,SMOOTH_MODE__DONT_SMOOTH);
        }
        
        if (use_alpha){
            set_alpha(ALPHA_MODE__BLEND);
            }else{
            set_alpha(ALPHA_MODE__DONT_BLEND);
        }
        
        set_depthbuffer(DEPTHBUFFER_MODE__OFF);
        set_cull_mode(CULL_MODE__NONE);
        
        set_texture_wrap(TEXTURE_WRAP_MODE__DONT_WRAP);
        
        //adjust for render (x2 & y2 need to be one greater than the destination offset)
        dst_x2++; dst_y2++;
        
        if (src_hardware_img->source_state.PO2_fix){
            src_w=src_hardware_img->PO2_w;
            src_h=src_hardware_img->PO2_h;
        }
        
        //calc source texture co-ordinates
        static float x1f,y1f,x2f,y2f;
        if (src_x1<=src_x2){
            x1f=((float)src_x1+0.01f)/(float)src_w;
            x2f=((float)src_x2+0.99f)/(float)src_w;
            }else{
            x2f=((float)src_x2+0.01f)/(float)src_w;
            x1f=((float)src_x1+0.99f)/(float)src_w;
        }
        if (src_y1<=src_y2){
            y1f=((float)src_y1+0.01f)/(float)src_h;
            y2f=((float)src_y2+0.99f)/(float)src_h;
            }else{
            y2f=((float)src_y2+0.01f)/(float)src_h;
            y1f=((float)src_y1+0.99f)/(float)src_h;
        }
        
        //expand buffers if necessary
        if ((hardware_buffer_vertices_count+18)>hardware_buffer_vertices_max){
            hardware_buffer_vertices_max=hardware_buffer_vertices_max*2+18;
            hardware_buffer_vertices=(float*)realloc(hardware_buffer_vertices,hardware_buffer_vertices_max*sizeof(float));
        }
        if ((hardware_buffer_texcoords_count+12)>hardware_buffer_texcoords_max){
            hardware_buffer_texcoords_max=hardware_buffer_texcoords_max*2+12;
            hardware_buffer_texcoords=(float*)realloc(hardware_buffer_texcoords,hardware_buffer_texcoords_max*sizeof(float));
        }
        
        //clockwise
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x1; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y1;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x2; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y1;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x1; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y2;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x1f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y1f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x2f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y1f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x1f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y2f;
        
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x1; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y2;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x2; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y1;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x2; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y2;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x1f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y2f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x2f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y1f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x2f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y2f;
        
        
        //hardware_buffer_flush(); //uncomment for debugging only
        
    }
    
    
    
    
    
    void hardware_img_tri2d(float dst_x1,float dst_y1,float dst_x2,float dst_y2,float dst_x3,float dst_y3,
    int32 src_img,int32 dst_img,
    float src_x1,float src_y1,float src_x2,float src_y2,float src_x3,float src_y3,
    int32 use_alpha,
    int32 smooth
    ){
        
        if (dst_img<0) dst_img=0;//both layers render to the primary context
        
        set_render_dest(dst_img);
        set_view(VIEW_MODE__2D);
        
        if (dst_img){
            static hardware_img_struct* dst_hardware_img;
            dst_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,dst_img);
            
            static int32 dst_w,dst_h;
            dst_w=dst_hardware_img->w;
            dst_h=dst_hardware_img->h;
            //SEAMLESS adjustments:
            //reduce texture co-ordinates (maintaining top-left)
            //(todo)
            //NON-SEAMLESS adjustments:
            //Extend rhs/bottom row to fill extra pixel space
            //calculate extents
            int32 rx1;
            int32 rx2;
            rx1=dst_x1;
            if (dst_x2<rx1){
                rx1=dst_x2;
            }
            if (dst_x3<rx1){
                rx1=dst_x3;
            }
            rx2=dst_x1;
            if (dst_x2>rx2){
                rx2=dst_x2;
            }
            if (dst_x3>rx2){
                rx2=dst_x3;
            }
            float xr;//the multiplier for where we should be (1=no change)
            if (rx1==rx2){
                xr=1.0f;
                }else{
                xr=((float)rx2-(float)rx1+1.0)/((float)rx2-(float)rx1);
            }
            int32 ry1;
            int32 ry2;
            ry1=dst_y1;
            if (dst_y2<ry1){
                ry1=dst_y2;
            }
            if (dst_y3<ry1){
                ry1=dst_y3;
            }
            ry2=dst_y1;
            if (dst_y2>ry2){
                ry2=dst_y2;
            }
            if (dst_y3>ry2){
                ry2=dst_y3;
            }
            float yr;//the multiplier for where we should be (1=no change)
            if (ry1==ry2){
                yr=1.0f;
                }else{
                yr=((float)ry2-(float)ry1+1.0f)/((float)ry2-(float)ry1);
            }
            //apply multipliers so right-most and bottom-most rows will be filled
            static int32 basex;
            basex=rx1;
            dst_x1=qbr_float_to_long(
            ((float)(dst_x1-rx1))*xr+(float)basex
            );
            dst_x2=qbr_float_to_long(
            ((float)(dst_x2-rx1))*xr+(float)basex
            );
            dst_x3=qbr_float_to_long(
            ((float)(dst_x3-rx1))*xr+(float)basex
            );
            static int32 basey;
            basey=ry1;
            dst_y1=qbr_float_to_long(
            ((float)(dst_y1-ry1))*yr+(float)basey
            );
            dst_y2=qbr_float_to_long(
            ((float)(dst_y2-ry1))*yr+(float)basey
            );
            dst_y3=qbr_float_to_long(
            ((float)(dst_y3-ry1))*yr+(float)basey
            );
            
            }else{ //dest is 0
            
            static int32 dst_w,dst_h;
            dst_w=environment__window_width;
            dst_h=environment__window_height;
            //SEAMLESS adjustments:
            //reduce texture co-ordinates (maintaining top-left)
            //(todo)
            //NON-SEAMLESS adjustments:
            //Extend rhs/bottom row to fill extra pixel space
            //calculate extents
            int32 rx1;
            int32 rx2;
            rx1=dst_x1;
            if (dst_x2<rx1){
                rx1=dst_x2;
            }
            if (dst_x3<rx1){
                rx1=dst_x3;
            }
            rx2=dst_x1;
            if (dst_x2>rx2){
                rx2=dst_x2;
            }
            if (dst_x3>rx2){
                rx2=dst_x3;
            }
            float xr;//the multiplier for where we should be (1=no change)
            if (rx1==rx2){
                xr=1.0f;
                }else{
                xr=((float)rx2-(float)rx1+1.0)/((float)rx2-(float)rx1);
            }
            int32 ry1;
            int32 ry2;
            ry1=dst_y1;
            if (dst_y2<ry1){
                ry1=dst_y2;
            }
            if (dst_y3<ry1){
                ry1=dst_y3;
            }
            ry2=dst_y1;
            if (dst_y2>ry2){
                ry2=dst_y2;
            }
            if (dst_y3>ry2){
                ry2=dst_y3;
            }
            float yr;//the multiplier for where we should be (1=no change)
            if (ry1==ry2){
                yr=1.0f;
                }else{
                yr=((float)ry2-(float)ry1+1.0f)/((float)ry2-(float)ry1);
            }
            //apply multipliers so right-most and bottom-most rows will be filled
            static int32 basex;
            basex=
            qbr_float_to_long(
            ((float)(rx1))*environment_2d__screen_x_scale+(float)environment_2d__screen_x1
            );
            dst_x1=
            basex+
            qbr_float_to_long(
            ((float)(dst_x1-rx1))*environment_2d__screen_x_scale*xr
            );
            dst_x2=
            basex+
            qbr_float_to_long(
            ((float)(dst_x2-rx1))*environment_2d__screen_x_scale*xr
            );
            dst_x3=
            basex+
            qbr_float_to_long(
            ((float)(dst_x3-rx1))*environment_2d__screen_x_scale*xr
            );
            static int32 basey;
            basey=
            qbr_float_to_long(
            ((float)(ry1))*environment_2d__screen_y_scale+(float)environment_2d__screen_y1
            );
            dst_y1=
            basey+
            qbr_float_to_long(
            ((float)(dst_y1-ry1))*environment_2d__screen_y_scale*yr
            );
            dst_y2=
            basey+
            qbr_float_to_long(
            ((float)(dst_y2-ry1))*environment_2d__screen_y_scale*yr
            );
            dst_y3=
            basey+
            qbr_float_to_long(
            ((float)(dst_y3-ry1))*environment_2d__screen_y_scale*yr
            );
            
        }
        
        set_render_source(src_img);
        
        static hardware_img_struct* src_hardware_img;
        static int32 src_h,src_w;
        src_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,src_img);
        src_h=src_hardware_img->h;
        src_w=src_hardware_img->w;
        
        if (smooth==0){
            set_smooth(SMOOTH_MODE__DONT_SMOOTH,SMOOTH_MODE__DONT_SMOOTH);
        }
        if (smooth==1){
            set_smooth(SMOOTH_MODE__SMOOTH,SMOOTH_MODE__SMOOTH);
        }
        if (smooth==2){
            set_smooth(SMOOTH_MODE__SMOOTH,SMOOTH_MODE__DONT_SMOOTH);
        }
        if (smooth==3){
            set_smooth(SMOOTH_MODE__DONT_SMOOTH,SMOOTH_MODE__SMOOTH);
        }
        
        set_texture_wrap(TEXTURE_WRAP_MODE__WRAP);
        
        if (use_alpha){
            set_alpha(ALPHA_MODE__BLEND);
            }else{
            set_alpha(ALPHA_MODE__DONT_BLEND);
        }
        
        set_depthbuffer(DEPTHBUFFER_MODE__OFF);
        set_cull_mode(CULL_MODE__NONE);
        
        if (src_hardware_img->source_state.PO2_fix){
            src_w=src_hardware_img->PO2_w;
            src_h=src_hardware_img->PO2_h;
        }
        
        //calc source texture co-ordinates
        static float x1f,y1f,x2f,y2f,x3f,y3f;
        x1f=((float)src_x1+0.5f)/(float)src_w;
        x2f=((float)src_x2+0.5f)/(float)src_w;
        x3f=((float)src_x3+0.5f)/(float)src_w;
        y1f=((float)src_y1+0.5f)/(float)src_h;
        y2f=((float)src_y2+0.5f)/(float)src_h;
        y3f=((float)src_y3+0.5f)/(float)src_h;
        
        
        //expand buffers if necessary
        if ((hardware_buffer_vertices_count+9)>hardware_buffer_vertices_max){
            hardware_buffer_vertices_max=hardware_buffer_vertices_max*2+9;
            hardware_buffer_vertices=(float*)realloc(hardware_buffer_vertices,hardware_buffer_vertices_max*sizeof(float));
        }
        if ((hardware_buffer_texcoords_count+6)>hardware_buffer_texcoords_max){
            hardware_buffer_texcoords_max=hardware_buffer_texcoords_max*2+6;
            hardware_buffer_texcoords=(float*)realloc(hardware_buffer_texcoords,hardware_buffer_texcoords_max*sizeof(float));
        }
        
        
        
        //clockwise
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x1; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y1;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x2; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y2;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x3; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y3;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x1f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y1f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x2f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y2f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x3f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y3f;
        
        //hardware_buffer_flush(); //uncomment for debugging only
        
    }
    
    void clear_depthbuffer(int32 dst_img){
        hardware_buffer_flush();
        if (dst_img<0) dst_img=0;//both layers render to the primary context
        set_render_dest(dst_img);
        if (dst_img>0){
            hardware_img_requires_depthbuffer((hardware_img_struct*)list_get(hardware_img_handles,dst_img));
        }
        glClear(GL_DEPTH_BUFFER_BIT);
    }
    
    void hardware_img_tri3d(float dst_x1,float dst_y1,float dst_z1,float dst_x2,float dst_y2,float dst_z2,float dst_x3,float dst_y3,float dst_z3,
    int32 src_img,int32 dst_img,
    float src_x1,float src_y1,float src_x2,float src_y2,float src_x3,float src_y3,
    int32 use_alpha,
    int32 smooth,
    int32 cull_mode,
    int32 depthbuffer_mode
    ){
        
        if (dst_img<0) dst_img=0;//both layers render to the primary context
        
        set_render_dest(dst_img);
        set_view(VIEW_MODE__3D);
        
        if (dst_img){
            static hardware_img_struct* dst_hardware_img;
            dst_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,dst_img);
            hardware_img_requires_depthbuffer(dst_hardware_img);
            }else{ //dest is 0
        }
        
        set_render_source(src_img);
        
        static hardware_img_struct* src_hardware_img;
        static int32 src_h,src_w;
        src_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,src_img);
        src_h=src_hardware_img->h;
        src_w=src_hardware_img->w;
        
        if (smooth==0){
            set_smooth(SMOOTH_MODE__DONT_SMOOTH,SMOOTH_MODE__DONT_SMOOTH);
        }
        if (smooth==1){
            set_smooth(SMOOTH_MODE__SMOOTH,SMOOTH_MODE__SMOOTH);
        }
        if (smooth==2){
            set_smooth(SMOOTH_MODE__SMOOTH,SMOOTH_MODE__DONT_SMOOTH);
        }
        if (smooth==3){
            set_smooth(SMOOTH_MODE__DONT_SMOOTH,SMOOTH_MODE__SMOOTH);
        }
        
        set_texture_wrap(TEXTURE_WRAP_MODE__WRAP);
        
        if (use_alpha){
            set_alpha(ALPHA_MODE__BLEND);
            }else{
            set_alpha(ALPHA_MODE__DONT_BLEND);
        }
        
        set_depthbuffer(depthbuffer_mode);
        
        //on frame buffers the 3D perspective is flipped vertically reversing the cull direction
        if (dst_img>0){
            if (cull_mode==CULL_MODE__CLOCKWISE_ONLY){
                cull_mode=CULL_MODE__ANTICLOCKWISE_ONLY;
                }else{
                if (cull_mode==CULL_MODE__ANTICLOCKWISE_ONLY) cull_mode=CULL_MODE__CLOCKWISE_ONLY;
            }
        }
        
        set_cull_mode(cull_mode);
        
        if (src_hardware_img->source_state.PO2_fix){
            src_w=src_hardware_img->PO2_w;
            src_h=src_hardware_img->PO2_h;
        }
        
        //calc source texture co-ordinates
        static float x1f,y1f,x2f,y2f,x3f,y3f;
        x1f=((float)src_x1+0.5f)/(float)src_w;
        x2f=((float)src_x2+0.5f)/(float)src_w;
        x3f=((float)src_x3+0.5f)/(float)src_w;
        y1f=((float)src_y1+0.5f)/(float)src_h;
        y2f=((float)src_y2+0.5f)/(float)src_h;
        y3f=((float)src_y3+0.5f)/(float)src_h;
        
        //expand buffers if necessary
        if ((hardware_buffer_vertices_count+9)>hardware_buffer_vertices_max){
            hardware_buffer_vertices_max=hardware_buffer_vertices_max*2+9;
            hardware_buffer_vertices=(float*)realloc(hardware_buffer_vertices,hardware_buffer_vertices_max*sizeof(float));
        }
        if ((hardware_buffer_texcoords_count+6)>hardware_buffer_texcoords_max){
            hardware_buffer_texcoords_max=hardware_buffer_texcoords_max*2+6;
            hardware_buffer_texcoords=(float*)realloc(hardware_buffer_texcoords,hardware_buffer_texcoords_max*sizeof(float));
        }
        
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x1; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y1; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_z1;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x2; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y2; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_z2;
        hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_x3; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_y3; hardware_buffer_vertices[hardware_buffer_vertices_count++]=dst_z3;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x1f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y1f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x2f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y2f;
        hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=x3f; hardware_buffer_texcoords[hardware_buffer_texcoords_count++]=y3f;
        //hardware_buffer_flush(); //uncomment for debugging only
        
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    static int32 software_screen_hardware_frame=0;
    
    static int32 in_GLUT_DISPLAY_REQUEST=0;
    
    void GLUT_DISPLAY_REQUEST(){
        
        if (in_GLUT_DISPLAY_REQUEST){
            return;
        }
        in_GLUT_DISPLAY_REQUEST=1;
        
        #ifdef QB64_MACOSX
            if (temp_window_title_set==1) {
                glutSetWindowTitle((char*)window_title);
                temp_window_title_set=0;
            }
        #endif

        //general use variables
        static int32 i,i2,i3;
        static int32 x,y,x2,y2;
        
        //determine which software frame to display
        static int32 last_i;//the last software frame displayed
        last_i=-1;
        for (i2=0;i2<=2;i2++){
            if (display_frame[i2].state==DISPLAY_FRAME_STATE__DISPLAYING){
                last_i=i2;
            }
        }
        i=-1;
        static int64 highest_order;
        highest_order=0;
        if (last_i!=-1) highest_order=display_frame[last_i].order;//avoid any frames below the current one
        for (i2=0;i2<=2;i2++){
            if (display_frame[i2].state==DISPLAY_FRAME_STATE__READY&&display_frame[i2].order>highest_order){
                highest_order=display_frame[i2].order;
                i=i2;
            }
        }
        if (i==-1) i=last_i;
        if (i==-1){
            in_GLUT_DISPLAY_REQUEST=0;
            return;//no frames exist yet, so screen size cannot be determined, therefore no action possible
        }
        if (i!=last_i){
            for (i2=0; i2<=2;i2++){
                if (display_frame[i2].order<display_frame[i].order&&(display_frame[i2].state==DISPLAY_FRAME_STATE__DISPLAYING||display_frame[i2].state==DISPLAY_FRAME_STATE__READY)) display_frame[i2].state=DISPLAY_FRAME_STATE__EMPTY;
            }
            display_frame[i].state=DISPLAY_FRAME_STATE__DISPLAYING;
        }
        
        
        static int64 order;
        order=last_hardware_display_frame_order;
        
        static int32 first_command_prev_order=0;
        static int32 rerender_prev_hardware_frame=0;
        rerender_prev_hardware_frame=0;
        
        //if no new software frame, only proceed if there is _GL content to render
        if (last_rendered_hardware_display_frame_order==last_hardware_display_frame_order){
            if (i==last_i){
                if (full_screen_set==-1){//no pending full-screen changes
                    if (os_resize_event==0){//no resize events
                        #ifndef DEPENDENCY_GL //we aren't using SUB _GL
                            in_GLUT_DISPLAY_REQUEST=0;
                            return;
                        #endif
                        if (displayorder_glrender==0){
                            in_GLUT_DISPLAY_REQUEST=0;
                            return;
                        }
                        if (first_command_prev_order){
                            rerender_prev_hardware_frame=1;
                            //reset next command to prev hardware frame's handle (if any)
                            last_hardware_command_rendered=first_command_prev_order;
                        }
                        
                    }
                }
            }
        }
        
        first_command_prev_order=0;
        
        
        //set environment variables
        environment_2d__screen_width=display_frame[i].w;
        environment_2d__screen_height=display_frame[i].h;
        
        
        
        
        
        
        
        
        
        
        
        os_resize_event=0;//turn off flag which forces a render to take place even if no content has changed
        
        
        
        
        
        if ((full_screen==0)&&(full_screen_set==-1)){//not in (or attempting to enter) full screen
            
            display_required_x=display_frame[i].w; display_required_y=display_frame[i].h;
            static int32 framesize_changed;
            framesize_changed=0;
            if ((display_required_x!=resize_snapback_x)||(display_required_y!=resize_snapback_y)) framesize_changed=1;
            
            
            resize_auto_ideal_aspect=(float)display_frame[i].w/(float)display_frame[i].h;
            resize_snapback_x=display_required_x; resize_snapback_y=display_required_y;
            
            
            
            if (resize_auto){
                //maintain aspect ratio
                static float ar;
                ar=(float)display_x/(float)display_y;
                if ((ar!=resize_auto_accept_aspect)&&(ar!=resize_auto_ideal_aspect)){
                    //set new size
                    static int32 x,y;
                    if (display_x_prev==display_x){
                        y=display_y;
                        x=(float)y*resize_auto_ideal_aspect;
                    }
                    if (display_y_prev==display_y){
                        x=display_x;
                        y=(float)x/resize_auto_ideal_aspect;
                    }
                    if ((display_y_prev!=display_y)&&(display_x_prev!=display_x)){
                        if (abs(display_y_prev-display_y)<abs(display_x_prev-display_x)){
                            x=display_x;
                            y=(float)x/resize_auto_ideal_aspect;
                            }else{
                            y=display_y;
                            x=(float)y*resize_auto_ideal_aspect;
                        }
                    }
                    resize_auto_accept_aspect=(float)x/(float)y;
                    resize_pending=1;
                    glutReshapeWindow(x,y);
                    glutPostRedisplay();
                    
                    
                    
                    goto auto_resized;
                }
            }//resize_auto
            
            
            
            if ((display_required_x!=display_x)||(display_required_y!=display_y)){
                if (resize_snapback||framesize_changed){
                    glutReshapeWindow(display_required_x,display_required_y);
                    glutPostRedisplay();
                    resize_pending=1;
                }
            }
            
            
            
            auto_resized:;
            
        }//not in (or attempting to enter) full screen
        
        //Pseudo-Fullscreen
        if (!resize_pending){//avoid switching to fullscreen before resize operations take effect
            if (full_screen_set!=-1){//full screen mode change requested
                if (full_screen_set==0){
                    if (full_screen!=0){
                        //exit full screen
                        resize_pending=1;
                        glutReshapeWindow(display_frame[i].w,display_frame[i].h);
                        glutPostRedisplay();
                    }
                    full_screen=0;
                    full_screen_set=-1;
                    }else{
                    if (full_screen==0){
                        glutFullScreen();
                    }
                    full_screen=full_screen_set;
                    full_screen_set=-1;
                }//enter full screen
            }//full_screen_set check
        }//size pending check
        
        
        
        
        
        
        //This code is deprecated but kept for reference purposes
        // 1) It was found to be unstable
        // 2) Switching modes means a high chance of losing pre-loaded OpenGL hardware textures/surfaces
        /*
            static int32 glut_window;
            //fullscreen
            if (!resize_pending){//avoid switching to fullscreen before resize operations take effect
            if (full_screen_set!=-1){//full screen mode change requested
            if (full_screen_set==0){
            //exit full screen
            glutLeaveGameMode();
            glutSetWindow(glut_window);
            reinit_glut_callbacks();
            full_screen=0;
            full_screen_set=-1;
            return;
            }else{
            static char game_mode_string[1000];
            static int32 game_mode_string_i;
            game_mode_string_i=0;
            game_mode_string_i+=sprintf(&game_mode_string[game_mode_string_i], "%d", display_frame[i].w);
            game_mode_string[game_mode_string_i++]=120;//"x"
            game_mode_string_i+=sprintf(&game_mode_string[game_mode_string_i], "%d", display_frame[i].h);
            game_mode_string[game_mode_string_i++]=58;//":"
            game_mode_string_i+=sprintf(&game_mode_string[game_mode_string_i], "%d", 32);
            glutGameModeString(game_mode_string);
            if(glutGameModeGet(GLUT_GAME_MODE_POSSIBLE)){
            //full screen using native dimensions which match the frame size
            if (full_screen==0) glut_window=glutGetWindow();
            glutEnterGameMode();
            fullscreen_width=display_frame[i].w; fullscreen_height=display_frame[i].h;
            reinit_glut_callbacks();
            full_screen=full_screen_set;//it's currently irrelavent if it is stretched or 1:1
            full_screen_set=-1;
            return;
            }else{ //native dimensions not possible
            //attempt full screen using desktop dimensions
            static int32 w; w=glutGet(GLUT_SCREEN_WIDTH);
            static int32 h; h=glutGet(GLUT_SCREEN_HEIGHT);
            game_mode_string_i=0;
            game_mode_string_i+=sprintf(&game_mode_string[game_mode_string_i], "%d", w);
            game_mode_string[game_mode_string_i++]=120;//"x"
            game_mode_string_i+=sprintf(&game_mode_string[game_mode_string_i], "%d", h);
            game_mode_string[game_mode_string_i++]=58;//":"
            game_mode_string_i+=sprintf(&game_mode_string[game_mode_string_i], "%d", 32);
            glutGameModeString(game_mode_string);
            if(glutGameModeGet(GLUT_GAME_MODE_POSSIBLE)){
            //full screen using desktop dimensions
            if (full_screen==0) glut_window=glutGetWindow();
            glutEnterGameMode();
            fullscreen_width=w; fullscreen_height=h;
            reinit_glut_callbacks();
            screen_scale=full_screen_set;
            full_screen=full_screen_set;
            full_screen_set=-1;
            return;
            }else{
            //cannot enter full screen
            full_screen=0;
            full_screen_set=-1;
            }
            }
            }//enter full screen
            }//full_screen_set check
            }//size pending check
        */
        
        
        
        
        
        
        
        //set window environment variables
        environment__window_width=display_x;
        environment__window_height=display_y;
        
        prepare_environment_2d();
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        //need a few variables here
        
        
        
        
        static int32 first_hardware_layer_rendered;
        static int32 first_hardware_layer_command;
        first_hardware_layer_rendered=0;
        first_hardware_layer_command=0;
        
        static int32 level; for (level=0; level<=5; level++){
            
            static int32 x1,y1,x2,y2;
            
            if (level==0){
                set_render_dest(0);
                glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
                }else{
                
                if (level==displayorder_glrender){
                    #ifdef DEPENDENCY_GL
                        
                        set_view(VIEW_MODE__RESET);
                        
                        if (close_program||dont_call_sub_gl||suspend_program||stop_program) goto abort_gl;
                        display_lock_request++;
                        while (display_lock_confirmed<display_lock_request){
                            if (close_program||dont_call_sub_gl||suspend_program||stop_program) goto abort_gl;
                            qbevent=1; Sleep(0);
                        }
                        sub_gl_called=1;
                        SUB__GL();
                        sub_gl_called=0;
                        abort_gl:;
                        display_lock_released=display_lock_confirmed;
                        
                    #endif //DEPENDENCY_GL
                }//level==displayorder_glrender
                
                
                if (level==displayorder_screen){//defaults to 1
                    
                    if (software_screen_hardware_frame!=0&&i!=last_i){
                        free_hardware_img(software_screen_hardware_frame, 847001);
                    }
                    if (i!=last_i||software_screen_hardware_frame==0){
                        software_screen_hardware_frame=new_hardware_img(display_frame[i].w, display_frame[i].h,display_frame[i].bgra,NULL);
                    }
                    
                    static hardware_img_struct* f1;
                    f1=(hardware_img_struct*)list_get(hardware_img_handles,software_screen_hardware_frame);
                    if (software_screen_hardware_frame==0){
                        alert("Invalid software_screen_hardware_frame!!");
                    }
                    if (f1==NULL) alert("Invalid software_screen_hardware_frame!");
                    
                    static int32 use_alpha;
                    use_alpha=0; if (level>1) use_alpha=1;
                    
                    
                    
                    //put the software screen
                    hardware_img_put(0,0,environment_2d__screen_width-1,environment_2d__screen_height-1,
                    software_screen_hardware_frame, 0,
                    0,0,f1->w-1,f1->h-1,
                    use_alpha,environment_2d__screen_smooth);
                    hardware_buffer_flush();
                    
                    
                }//level==displayorder_screen
                
                
                if (level==displayorder_hardware||level==displayorder_hardware1){
                    
                    static int32 dst;
                    dst=0; if (level==displayorder_hardware1) dst=-1;
                    
                    static int32 command;
                    command=0;
                    
                    static int32 caller_flag;
                    caller_flag=0;
                    
                    if (first_hardware_layer_rendered==0){
                        
                        if (first_hardware_command){
                            
                            if (last_hardware_command_rendered){
                                
                                if (rerender_prev_hardware_frame){
                                    command=last_hardware_command_rendered;
                                    caller_flag=100;
                                    }else{
                                    hardware_graphics_command_struct* last_hgc=(hardware_graphics_command_struct*)list_get(hardware_graphics_command_handles,last_hardware_command_rendered);
                                    if (last_hgc==NULL) alert("Rendering: Last HGC is NULL!");
                                    command=last_hgc->next_command;
                                    caller_flag=200;
                                }
                                
                                }else{
                                
                                command=first_hardware_command;
                                caller_flag=300;
                            }
                            
                            //process/skip pending hardware puts before this frame's order value
                            while (command){
                                hardware_graphics_command_struct* hgc=(hardware_graphics_command_struct*)list_get(hardware_graphics_command_handles,command);
                                if (hgc->order<order){
                                    
                                    if (hgc->command==HARDWARE_GRAPHICS_COMMAND__FREEIMAGE){
                                        free_hardware_img(hgc->src_img, 847002+caller_flag);
                                    }
                                    
                                    if (hgc->command==HARDWARE_GRAPHICS_COMMAND__PUTIMAGE){
                                        if (hgc->dst_img>0){ //note: rendering to the old default surface is pointless, but renders onto maintained hardware images are still required
                                            hardware_img_put(hgc->dst_x1,hgc->dst_y1,hgc->dst_x2,hgc->dst_y2,
                                            hgc->src_img, hgc->dst_img,
                                            hgc->src_x1,hgc->src_y1,hgc->src_x2,hgc->src_y2,
                                            hgc->use_alpha,hgc->smooth);
                                        }
                                    }
                                    
                                    if (hgc->command==HARDWARE_GRAPHICS_COMMAND__MAPTRIANGLE){
                                        if (hgc->dst_img>0){ //note: rendering to the old default surface is pointless, but renders onto maintained hardware images are still required
                                            hardware_img_tri2d(hgc->dst_x1,hgc->dst_y1,hgc->dst_x2,hgc->dst_y2,hgc->dst_x3,hgc->dst_y3,
                                            hgc->src_img, hgc->dst_img,
                                            hgc->src_x1,hgc->src_y1,hgc->src_x2,hgc->src_y2,hgc->src_x3,hgc->src_y3,
                                            hgc->use_alpha,hgc->smooth);
                                        }
                                    }
                                    
                                    if (hgc->command==HARDWARE_GRAPHICS_COMMAND__MAPTRIANGLE3D){
                                        if (hgc->dst_img>0){ //note: rendering to the old default surface is pointless, but renders onto maintained hardware images are still required
                                            hardware_img_tri3d(hgc->dst_x1,hgc->dst_y1,hgc->dst_z1,hgc->dst_x2,hgc->dst_y2,hgc->dst_z2,hgc->dst_x3,hgc->dst_y3,hgc->dst_z3,
                                            hgc->src_img, hgc->dst_img,
                                            hgc->src_x1,hgc->src_y1,hgc->src_x2,hgc->src_y2,hgc->src_x3,hgc->src_y3,
                                            hgc->use_alpha,hgc->smooth,hgc->cull_mode,hgc->depthbuffer_mode);
                                        }
                                    }
                                    
                                    if (hgc->command==HARDWARE_GRAPHICS_COMMAND__CLEAR_DEPTHBUFFER){
                                        if (hgc->dst_img>0){ //note: rendering to the old default surface is pointless, but renders onto maintained hardware images are still required
                                            clear_depthbuffer(hgc->dst_img);
                                        }
                                    }
                                    
                                    last_hardware_command_rendered=command;
                                    if (next_hardware_command_to_remove==0) next_hardware_command_to_remove=command;
                                    command=hgc->next_command;
                                    hgc->remove=1;
                                    }else{
                                    goto found_command_from_current_order;
                                }
                            }
                            found_command_from_current_order:;
                            
                        }//first_hardware_command
                        
                        
                        first_hardware_layer_command=command;
                        }else{
                        command=first_hardware_layer_command;
                    }
                    
                    //process pending hardware puts for this frame's order value
                    while (command){
                        hardware_graphics_command_struct* hgc=(hardware_graphics_command_struct*)list_get(hardware_graphics_command_handles,command);
                        if (hgc==NULL){
                            
                            hardware_graphics_command_struct* hgcx=(hardware_graphics_command_struct*)list_get(hardware_graphics_command_handles,next_hardware_command_to_remove);
                            alert(order);
                            alert(hgcx->order);
                            alert(command);
                            alert ("Renderer: Command does not exist.");
                        }
                        if (hgc->order==order){
                            if (first_command_prev_order==0) first_command_prev_order=command;
                            
                            if (hgc->command==HARDWARE_GRAPHICS_COMMAND__FREEIMAGE&&rerender_prev_hardware_frame==0&&first_hardware_layer_rendered==0){
                                free_hardware_img(hgc->src_img, 847003);
                            }
                            
                            if (hgc->command==HARDWARE_GRAPHICS_COMMAND__PUTIMAGE){
                                if (rerender_prev_hardware_frame==0||hgc->dst_img<=0){
                                    if ((hgc->dst_img>0&&first_hardware_layer_rendered==0)||hgc->dst_img==dst){
                                        hardware_img_put(hgc->dst_x1,hgc->dst_y1,hgc->dst_x2,hgc->dst_y2,
                                        hgc->src_img, hgc->dst_img,
                                        hgc->src_x1,hgc->src_y1,hgc->src_x2,hgc->src_y2,
                                        hgc->use_alpha,hgc->smooth);
                                    }
                                }
                            }
                            
                            if (hgc->command==HARDWARE_GRAPHICS_COMMAND__MAPTRIANGLE){
                                if (rerender_prev_hardware_frame==0||hgc->dst_img<=0){
                                    if ((hgc->dst_img>0&&first_hardware_layer_rendered==0)||hgc->dst_img==dst){
                                        hardware_img_tri2d(hgc->dst_x1,hgc->dst_y1,hgc->dst_x2,hgc->dst_y2,hgc->dst_x3,hgc->dst_y3,
                                        hgc->src_img, hgc->dst_img,
                                        hgc->src_x1,hgc->src_y1,hgc->src_x2,hgc->src_y2,hgc->src_x3,hgc->src_y3,
                                        hgc->use_alpha,hgc->smooth);
                                    }
                                }
                            }
                            
                            if (hgc->command==HARDWARE_GRAPHICS_COMMAND__MAPTRIANGLE3D){
                                if (rerender_prev_hardware_frame==0||hgc->dst_img<=0){
                                    if ((hgc->dst_img>0&&first_hardware_layer_rendered==0)||hgc->dst_img==dst){
                                        hardware_img_tri3d(hgc->dst_x1,hgc->dst_y1,hgc->dst_z1,hgc->dst_x2,hgc->dst_y2,hgc->dst_z2,hgc->dst_x3,hgc->dst_y3,hgc->dst_z3,
                                        hgc->src_img, hgc->dst_img,
                                        hgc->src_x1,hgc->src_y1,hgc->src_x2,hgc->src_y2,hgc->src_x3,hgc->src_y3,
                                        hgc->use_alpha,hgc->smooth,hgc->cull_mode,hgc->depthbuffer_mode);
                                    }
                                }
                            }
                            
                            if (hgc->command==HARDWARE_GRAPHICS_COMMAND__CLEAR_DEPTHBUFFER){
                                if (rerender_prev_hardware_frame==0||hgc->dst_img<=0){
                                    if ((hgc->dst_img>0&&first_hardware_layer_rendered==0)||hgc->dst_img==dst){
                                        clear_depthbuffer(hgc->dst_img);
                                    }
                                }
                            }
                            
                            last_hardware_command_rendered=command;
                            if (next_hardware_command_to_remove==0) next_hardware_command_to_remove=command;//!!!! should be prev to this command
                            command=hgc->next_command;
                            hgc->remove=1;
                            }else{
                            goto finished_all_commands_for_current_frame;
                        }
                        
                        
                    }
                    finished_all_commands_for_current_frame:;
                    
                    first_hardware_layer_rendered=1;
                    
                    
                    
                    hardware_buffer_flush();
                }//level==displayorder_hardware||level==displayorder_hardware1
                
                
                if (level==5){
                    
                    if (environment_2d__letterbox){
                        
                        //create a black texture (if not yet created)
                        static uint32 black_pixel=0x00000000;
                        static int32 black_texture=0;
                        if (black_texture==0){
                            black_texture=new_hardware_img(1,1,&black_pixel,NULL);
                        }
                        
                        if (environment_2d__letterbox==1){
                            //vertical stripes
                            hardware_img_put(((float)-environment_2d__screen_x1)/environment_2d__screen_x_scale-1.0f,0,-1,environment_2d__screen_height-1,
                            black_texture, 0,
                            0,0,0,0,
                            0,0);
                            hardware_img_put(environment_2d__screen_width,0,(((float)-environment_2d__screen_x1)+(float)environment__window_width-1.0f)/environment_2d__screen_x_scale+1.0f,environment_2d__screen_height-1,
                            black_texture, 0,
                            0,0,0,0,
                            0,0);
                            }else{
                            //horizontal stripes
                            hardware_img_put(0,((float)-environment_2d__screen_y1)/environment_2d__screen_y_scale-1.0f,environment_2d__screen_width-1,-1,
                            black_texture, 0,
                            0,0,0,0,
                            0,0);
                            hardware_img_put(0,environment_2d__screen_height,environment_2d__screen_width-1,(((float)-environment_2d__screen_y1)+(float)environment__window_height-1.0f)/environment_2d__screen_y_scale+1.0f,
                            black_texture, 0,
                            0,0,0,0,
                            0,0);
                        }
                        hardware_buffer_flush();
                    }//letterbox
                    
                }//level==5
                
                
            }//level!=0
        }//level loop
        
        if (requestedKeyboardOverlayImage){
            int32 src=requestedKeyboardOverlayImage-HARDWARE_IMG_HANDLE_OFFSET;
            hardware_img_struct* src_hardware_img;
            src_hardware_img=(hardware_img_struct*)list_get(hardware_img_handles,src);
            /*
                hardware_img_put(0,0,src_hardware_img->w-1,src_hardware_img->h-1,
                src, 0,
                0,0,src_hardware_img->w-1,src_hardware_img->h-1,
                1,0);
            */
            hardware_img_put(0,0,environment_2d__screen_width-1,environment_2d__screen_height-1,
            src, 0,
            0,0,src_hardware_img->w-1,src_hardware_img->h-1,
            1,0);
            hardware_buffer_flush();
        }
        
        last_rendered_hardware_display_frame_order=last_hardware_display_frame_order;
        
        
        
        if (suspend_program){ //Otherwise skipped SUB__GL content becomes "invisible"
            //...
            }else{
            glutSwapBuffers();
        }
        
        in_GLUT_DISPLAY_REQUEST=0;
        
    }//GLUT_DISPLAY_REQUEST
    
    
    
    
    
    void GLUT_MouseButton_Up(int button,int x,int y){
        #ifdef QB64_GLUT
            int32 i;
            int32 handle;
            handle=mouse_message_queue_first;
            mouse_message_queue_struct *queue=(mouse_message_queue_struct*)list_get(mouse_message_queue_handles,handle);
            
            i=queue->last+1; if (i>queue->lastIndex) i=0;
            if (i==queue->current){
                int32 nextIndex=queue->last+1; if (nextIndex>queue->lastIndex) nextIndex=0;
                queue->current=nextIndex;
            }
            queue->queue[i].x=x;
            queue->queue[i].y=y;
            queue->queue[i].movementx=0;
            queue->queue[i].movementy=0;
            queue->queue[i].buttons=queue->queue[queue->last].buttons;
            if (queue->queue[i].buttons&(1<<(button-1))) queue->queue[i].buttons^=(1<<(button-1));
            queue->last=i;
            
            if (device_last){//core devices required?
                if ((button>=1)&&(button<=3)){
                    button--;
                    static device_struct *d;
                    d=&devices[2];//mouse
                    
                    int32 eventIndex=createDeviceEvent(d);
                    setDeviceEventButtonValue(d,eventIndex,button,0);
                    commitDeviceEvent(d);
                    
                }//valid range
            }//core devices required
            
        #endif
    }
    
    void GLUT_MouseButton_Down(int button,int x,int y){
        #ifdef QB64_GLUT
            
            int32 i;
            int32 handle;
            handle=mouse_message_queue_first;
            mouse_message_queue_struct *queue=(mouse_message_queue_struct*)list_get(mouse_message_queue_handles,handle);
            
            i=queue->last+1; if (i>queue->lastIndex) i=0;
            if (i==queue->current){
                int32 nextIndex=queue->last+1; if (nextIndex>queue->lastIndex) nextIndex=0;
                queue->current=nextIndex;
            }
            queue->queue[i].x=x;
            queue->queue[i].y=y;
            queue->queue[i].movementx=0;
            queue->queue[i].movementy=0;
            queue->queue[i].buttons=queue->queue[queue->last].buttons;
            queue->queue[i].buttons|=(1<<(button-1));
            queue->last=i;
            
            if (device_last){//core devices required?
                if ((button>=1)&&(button<=3)){
                    button--;
                    static device_struct *d;
                    d=&devices[2];//mouse
                    
                    int32 eventIndex=createDeviceEvent(d);
                    setDeviceEventButtonValue(d,eventIndex,button,1);
                    commitDeviceEvent(d);
                    
                    //1-3
                    }else{
                    //not 1-3
                    //mouse wheel?
                    if ((button>=4)&&(button<=5)){
                        static float f;
                        if (button==4) f=-1; else f=1;
                        static device_struct *d;
                        d=&devices[2];//mouse
                        
                        int32 eventIndex=createDeviceEvent(d);
                        setDeviceEventWheelValue(d,eventIndex,2,f);
                        commitDeviceEvent(d);
                        
                        eventIndex=createDeviceEvent(d);
                        setDeviceEventWheelValue(d,eventIndex,2,0);
                        commitDeviceEvent(d);
                        
                    }//4-5
                }//not 1-3
            }//core devices required
        #endif
    }
    
    void GLUT_MOUSE_FUNC(int glut_button,int state,int x,int y){
        #ifdef QB64_GLUT
            if (state==GLUT_DOWN) GLUT_MouseButton_Down(glut_button + 1,x,y);
            if (state==GLUT_UP) GLUT_MouseButton_Up(glut_button + 1,x,y);
        #endif
    }
    
    void GLUT_MOTION_FUNC(int x, int y){
        
        int32 i, last_i;
        int32 handle;
        int32 xrel, yrel;
        handle=mouse_message_queue_first;
        mouse_message_queue_struct *queue=(mouse_message_queue_struct*)list_get(mouse_message_queue_handles,handle);
        
        //message #1
        last_i=queue->last;
        i=queue->last+1; if (i>queue->lastIndex) i=0; //wrap around
        if (i==queue->current){
            int32 nextIndex=queue->last+1;
            if (nextIndex>queue->lastIndex) nextIndex=0;
            queue->current=nextIndex;
        }
        #ifdef QB64_WINDOWS
        // Windows calculates relative movement by intercepting WM_INPUT events instead
        xrel = 0;
        yrel = 0;
        #else
        xrel = x - queue->queue[queue->last].x;
        yrel = y - queue->queue[queue->last].y;
        #endif

        queue->queue[i].x=x;
        queue->queue[i].y=y;
        queue->queue[i].movementx=xrel;
        queue->queue[i].movementy=yrel;
        queue->queue[i].buttons=queue->queue[last_i].buttons;
        queue->last=i;
        
        //message #2 (clears movement values to avoid confusion)
        last_i=queue->last;
        i=queue->last+1; if (i>queue->lastIndex) i=0;
        if (i==queue->current){
            int32 nextIndex=queue->last+1; if (nextIndex>queue->lastIndex) nextIndex=0;
            queue->current=nextIndex;
        }
        queue->queue[i].x=x;
        queue->queue[i].y=y;
        queue->queue[i].movementx=0;
        queue->queue[i].movementy=0;
        queue->queue[i].buttons=queue->queue[last_i].buttons;
        queue->last=i;
        
        if (device_last){//core devices required?
            if (!device_mouse_relative){
                static device_struct *d;
                d=&devices[2];//mouse
                
                int32 eventIndex=createDeviceEvent(d);
                static float fx,fy;
                static int32 z;
                fx=x;
                fx-=x_offset;
                z=x_monitor-x_offset*2;
                if (fx<0) fx=0;
                if (fx>=z) fx=z-1;
                fx=fx/(float)(z-1);//0 to 1
                fx*=2.0;//0 to 2
                fx-=1.0;//-1 to 1
                fy=y;
                fy-=y_offset;
                z=y_monitor-y_offset*2;
                if (fy<0) fy=0;
                if (fy>=z) fy=z-1;
                fy=fy/(float)(z-1);//0 to 1
                fy*=2.0;//0 to 2
                fy-=1.0;//-1 to 1
                setDeviceEventAxisValue(d,eventIndex,0,fx);
                setDeviceEventAxisValue(d,eventIndex,1,fy);
                commitDeviceEvent(d);
                
                }else{
                static device_struct *d;
                d=&devices[2];//mouse
                
                int32 eventIndex=createDeviceEvent(d);
                static float fx,fy;
                static int32 z;
                fx=xrel;
                fy=yrel;
                setDeviceEventWheelValue(d,eventIndex,0,fx);
                setDeviceEventWheelValue(d,eventIndex,1,fy);
                commitDeviceEvent(d);
                
                eventIndex=createDeviceEvent(d);
                fx=0;
                fy=0;
                setDeviceEventWheelValue(d,eventIndex,0,fx);
                setDeviceEventWheelValue(d,eventIndex,1,fy);
                commitDeviceEvent(d);
                
            }
        }//core devices required
    }
    
    void GLUT_PASSIVEMOTION_FUNC(int x, int y){
        GLUT_MOTION_FUNC(x,y);
    }
    
    
    void GLUT_MOUSEWHEEL_FUNC(int wheel, int direction, int x, int y){
        #ifdef QB64_GLUT
            //Note: freeglut specific, limited documentation existed so the following research was done:
            //  qbs_print(qbs_str(wheel),NULL); <-- was always 0 [could 1 indicate horizontal wheel?]
            //  qbs_print(qbs_str(direction),NULL); <-- 1(up) or -1(down)
            //  qbs_print(qbs_str(x),NULL); <--mouse x,y co-ordinates
            //  qbs_print(qbs_str(y),1);    <
            if (direction>0){GLUT_MouseButton_Down(4,x,y); GLUT_MouseButton_Up(4,x,y);}
            if (direction<0){GLUT_MouseButton_Down(5,x,y); GLUT_MouseButton_Up(5,x,y);}
        #endif
    }
    
    
    
    
    
    
    
    
    
    
#endif
