]> git.tdb.fi Git - gldbg.git/blobdiff - source/grabber.cpp
Add gldQueryViewport and gldReadPixels calls
[gldbg.git] / source / grabber.cpp
diff --git a/source/grabber.cpp b/source/grabber.cpp
new file mode 100644 (file)
index 0000000..e56622f
--- /dev/null
@@ -0,0 +1,248 @@
+/* $Id$
+
+This file is part of gldbg
+Copyright © 2011  Mikko Rasa, Mikkosoft Productions
+Distributed under the GPL
+*/
+
+#include <algorithm>
+#include <png.h>
+#include <msp/io/print.h>
+#include "arraysize.h"
+#include "breakpoint.h"
+#include "functions.h"
+#include "gldbg.h"
+#include "grabber.h"
+
+using namespace std;
+using namespace Msp;
+
+Grabber::Grabber(GlDbg &g):
+       gldbg(g),
+       decoder(gldecoder_new(this, NULL)),
+       frame_num(1),
+       grab_num(1),
+       pending_grab(0),
+       autograb(false)
+{
+       fill(viewport, viewport+4, 0);
+
+       gldbg.get_command_interpreter().register_command("grab", this, &Grabber::cmd_grab)
+               .set_help("Grab framebuffer contents");
+       gldbg.get_command_interpreter().register_command("autograb", this, &Grabber::cmd_autograb)
+               .set_help("Automatically grab framebuffer contents",
+                       "autograb frame\n"
+                       "  Grab at the end of every frame\n\n"
+                       "autograb draw\n"
+                       "  Grab after every draw call\n\n"
+                       "autograb off\n"
+                       "  Turn off automatic grabbing\n");
+
+       decoder->glViewport = glViewport;
+       decoder->glGetIntegerv = glGetIntegerv;
+       decoder->glReadPixels = glReadPixels;
+       decoder->gldBreak = gldBreak;
+
+       flavor_init();
+}
+
+void Grabber::decode(const char *data, unsigned len)
+{
+       gldecoder_decode(decoder, data, len);
+}
+
+void Grabber::process_started()
+{
+       if(autograb)
+       {
+               gldbg.set_breakpoint(break_funcs[0], BREAK_CALL, this);
+               if(autograb==2)
+               {
+                       for(unsigned i=1; break_funcs[i]; ++i)
+                               gldbg.set_breakpoint(break_funcs[i], BREAK_RETURN, this);
+               }
+       }
+
+       frame_num = 1;
+       grab_num = 1;
+}
+
+void Grabber::process_stopped(int reason)
+{
+       if((reason>>8)==3 && pending_grab)
+       {
+               grab();
+               if(pending_grab==2)
+               {
+                       gldbg.hold();
+                       gldbg.get_process().resume();
+               }
+               else
+                       gldbg.resume_from_break(this);
+               pending_grab = 0;
+       }
+}
+
+void Grabber::cmd_grab(const string &)
+{
+       grab();
+       if(pending_grab)
+               ++pending_grab;
+       else
+       {
+               gldbg.hold();
+               gldbg.get_process().resume();
+       }
+}
+
+void Grabber::cmd_autograb(const string &args)
+{
+       if(args=="off")
+       {
+               autograb = 0;
+               if(gldbg.get_process().get_state()!=Process::INACTIVE)
+               {
+                       gldbg.clear_breakpoint(break_funcs[0], BREAK_CALL, this);
+                       for(unsigned i=1; break_funcs[i]; ++i)
+                               gldbg.clear_breakpoint(break_funcs[i], BREAK_RETURN, this);
+               }
+       }
+       else if(args=="frame")
+       {
+               autograb = 1;
+               if(gldbg.get_process().get_state()!=Process::INACTIVE)
+                       gldbg.set_breakpoint(break_funcs[0], BREAK_CALL, this);
+       }
+       else if(args=="draw")
+       {
+               autograb = 2;
+               if(gldbg.get_process().get_state()!=Process::INACTIVE)
+               {
+                       gldbg.set_breakpoint(break_funcs[0], BREAK_CALL, this);
+                       for(unsigned i=1; break_funcs[i]; ++i)
+                               gldbg.set_breakpoint(break_funcs[i], BREAK_RETURN, this);
+               }
+       }
+       else
+               throw InvalidParameterValue("Invalid autograb mode");
+}
+
+void Grabber::grab()
+{
+       if(!viewport[2] || !viewport[3])
+       {
+               GlPacket *pkt = packet_begin(FUNC_GLDQUERYVIEWPORT);
+               gldbg.send(pkt);
+
+               gldbg.hold();
+               gldbg.get_process().resume();
+
+               pending_grab = 1;
+               return;
+       }
+
+       GlPacket *pkt = packet_begin(FUNC_GLDREADPIXELS);
+       for(unsigned i=0; i<4; ++i)
+               packet_write_int(pkt, viewport[i]);
+       packet_write_int(pkt, GL_RGBA);
+       packet_write_int(pkt, GL_UNSIGNED_BYTE);
+       gldbg.send(pkt);
+}
+
+int Grabber::write_png(const string &fn, unsigned width, unsigned height, void *data)
+{
+       FILE *out = fopen(fn.c_str(), "w");
+       if(!out)
+               return -1;
+
+       png_struct *png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+       if(!png)
+       {
+               fclose(out);
+               return -1;
+       }
+
+       png_info *info = png_create_info_struct(png);
+       if(!info)
+       {
+               png_destroy_write_struct(&png, 0);
+               fclose(out);
+               return -1;
+       }
+
+       if(setjmp(png_jmpbuf(png)))
+       {
+               png_destroy_write_struct(&png, &info);
+               fclose(out);
+               return -1;
+       }
+
+       png_init_io(png, out);
+       png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+       vector<png_byte *> row_ptrs(height);
+       for(unsigned i=0; i<height; ++i)
+               row_ptrs[i] = reinterpret_cast<png_byte *>(data)+(height-1-i)*width*4;
+       png_set_rows(png, info, &row_ptrs.front());
+
+       png_write_png(png, info, PNG_TRANSFORM_IDENTITY, 0);
+       png_write_end(png, info);
+
+       png_destroy_write_struct(&png, &info);
+       fclose(out);
+
+       return 0;
+}
+
+void Grabber::next_frame()
+{
+       ++frame_num;
+       grab_num = 1;
+}
+
+void Grabber::glViewport(void *user_data, int x, int y, int width, int height)
+{
+       Grabber *self = reinterpret_cast<Grabber *>(user_data);
+       self->viewport[0] = x;
+       self->viewport[1] = y;
+       self->viewport[2] = width;
+       self->viewport[3] = height;
+}
+
+void Grabber::glGetIntegerv(void *user_data, GLenum pname, int *params)
+{
+       Grabber *self = reinterpret_cast<Grabber *>(user_data);
+       if(pname==GL_VIEWPORT)
+               copy(params, params+4, self->viewport);
+}
+
+void Grabber::glReadPixels(void *user_data, int, int, int width, int height, GLenum format, GLenum type, void *data)
+{
+       Grabber *self = reinterpret_cast<Grabber *>(user_data);
+       
+       string fn = Msp::format("grab-%04d-%04d.png", self->frame_num, self->grab_num);
+       if(format==GL_RGBA && type==GL_UNSIGNED_BYTE)
+       {
+               if(self->write_png(fn, width, height, data)<0)
+                       IO::print("Failed to write png file %s\n", fn);
+               else
+                       IO::print("Grabbed %dx%d png image to %s\n", width, height, fn);
+       }
+
+       ++self->grab_num;
+}
+
+void Grabber::gldBreak(void *user_data, unsigned short func, unsigned char flag)
+{
+       Grabber *self = reinterpret_cast<Grabber *>(user_data);
+       if(!self->autograb)
+               return;
+
+       if(flag==BREAK_CALL && func==break_funcs[0])
+               self->pending_grab = 1;
+       else if(flag==BREAK_RETURN && self->autograb>=2)
+       {
+               for(unsigned i=1; (!self->pending_grab && break_funcs[i]); ++i)
+                       self->pending_grab = (func==break_funcs[i]);
+       }
+}