+#include "rtarg.h"
+
+struct RTStackItem {
+ RenderTarget *rt;
+ int saved_vp[4];
+};
+
+static void attach_depth_texture(Texture *tex);
+
+#define MAX_STACK_SIZE 16
+static RTStackItem rstack[MAX_STACK_SIZE];
+static int rtop;
+
+void set_render_target(RenderTarget *rt)
+{
+ if(rt) {
+ if(!rstack[rtop].rt) {
+ glGetIntegerv(GL_VIEWPORT, rstack[rtop].saved_vp);
+ }
+ rt->bind();
+
+ } else {
+ int *vp = rstack[rtop].saved_vp;
+ glViewport(vp[0], vp[1], vp[2], vp[3]);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+
+ rstack[rtop].rt = rt;
+}
+
+RenderTarget *current_render_target()
+{
+ return rstack[rtop].rt;
+}
+
+bool push_render_target(RenderTarget *rt)
+{
+ if(!rt) {
+ error_log("push_render_target(0) is invalid\n");
+ return false;
+ }
+ if(rtop >= MAX_STACK_SIZE - 1) {
+ warning_log("push_render_target: overflow\n");
+ return false;
+ }
+ int *vp = rstack[rtop + 1].saved_vp;
+ RenderTarget *prev = current_render_target();
+
+ if(prev) {
+ vp[0] = vp[1] = 0;
+ vp[2] = prev->get_width();
+ vp[3] = prev->get_height();
+ } else {
+ memcpy(vp, rstack[rtop].saved_vp, 4 * sizeof(int));
+ }
+ rstack[++rtop].rt = rt;
+ return true;
+}
+
+bool pop_render_target()
+{
+ if(rtop <= 0) {
+ error_log("pop_render_target: undeflow\n");
+ return false;
+ }
+
+ int *vp = rstack[rtop].saved_vp;
+ glViewport(vp[0], vp[1], vp[2], vp[3]);
+
+ if(rstack[--rtop].rt) {
+ rstack[rtop].rt->bind();
+ } else {
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+ return true;
+}
+
+RenderTarget::RenderTarget()
+{
+ fbo = 0;
+ rbdepth = 0;
+ width = height = tex_width = tex_height = 0;
+ auto_vport = true;
+ rtcount = 0;
+
+ for(int i=0; i<4; i++) {
+ tex[i] = 0;
+ own_texture[i] = true;
+ }
+}
+
+RenderTarget::~RenderTarget()
+{
+ destroy();
+}
+
+bool RenderTarget::create(int xsz, int ysz, unsigned int fmt, unsigned int flags)
+{
+ return create_mrt(xsz, ysz, 1, fmt, flags);
+}
+
+bool RenderTarget::create_mrt(int xsz, int ysz, int num, unsigned int fmt, unsigned int flags)
+{
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ width = xsz;
+ height = ysz;
+ tex_width = next_pow2(xsz);
+ tex_height = next_pow2(ysz);
+ rtcount = num;
+
+ texmat.scaling((float)width / (float)tex_width, (float)height / (float)tex_height, 1);
+
+ if(flags & RT_COLOR) {
+ for(int i=0; i<num; i++) {
+ tex[i] = new Texture;
+ tex[i]->create(tex_width, tex_height, TEX_2D, fmt);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
+ tex[i]->get_id(), 0);
+ own_texture[i] = true;
+ }
+ } else {
+ // in case set_texture was called before create
+ for(int i=0; i<num; i++) {
+ if(tex[i]) {
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D,
+ tex[i]->get_id(), 0);
+ }
+ }
+ }
+
+ if(flags & (RT_DEPTH | RT_STENCIL)) {
+ unsigned int fmt, attachment;
+
+ glGenRenderbuffers(1, &rbdepth);
+ glBindRenderbuffer(GL_RENDERBUFFER, rbdepth);
+
+ switch(flags & (RT_DEPTH | RT_STENCIL)) {
+ case RT_DEPTH:
+ fmt = GL_DEPTH_COMPONENT24;
+ attachment = GL_DEPTH_ATTACHMENT;
+ break;
+
+ case RT_STENCIL:
+ fmt = GL_STENCIL_INDEX8;
+ attachment = GL_STENCIL_ATTACHMENT;
+ break;
+
+ case RT_DEPTH | RT_STENCIL:
+ fmt = GL_DEPTH24_STENCIL8;
+ attachment = GL_DEPTH_STENCIL_ATTACHMENT;
+ break;
+ }
+
+ glRenderbufferStorage(GL_RENDERBUFFER, fmt, width, height);
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rbdepth);
+
+ } else if(depth) {
+ attach_depth_texture(depth);
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ return true;
+}
+
+void RenderTarget::destroy()
+{
+ for(int i=0; i<4; i++) {
+ if(tex[i] && own_texture[i]) {
+ delete tex[i];
+ tex[i] = 0;
+ }
+ }
+ if(rbdepth) {
+ glDeleteRenderbuffers(1, &rbdepth);
+ rbdepth = 0;
+ }
+ if(fbo) {
+ glDeleteFramebuffers(1, &fbo);
+ fbo = 0;
+ }
+}
+
+int RenderTarget::get_width() const
+{
+ return width;
+}
+
+int RenderTarget::get_height() const
+{
+ return height;
+}
+
+void RenderTarget::set_texture(Texture *tex, int idx)
+{
+ if(this->tex[idx] && own_texture[idx]) {
+ delete this->tex[idx];
+ }
+ this->tex[idx] = tex;
+ own_texture[idx] = false;
+
+ if(fbo) {
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + idx, GL_TEXTURE_2D,
+ tex->get_id(), 0);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+}
+
+void RenderTarget::set_depth_texture(Texture *tex)
+{
+ depth = tex;
+
+ if(fbo) {
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ attach_depth_texture(tex);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ }
+}
+
+Texture *RenderTarget::texture(int idx) const
+{
+ return tex[idx];
+}
+
+Texture *RenderTarget::depth_texture() const
+{
+ return depth;
+}
+
+void RenderTarget::set_auto_viewport(bool enable)
+{
+ auto_vport = enable;
+}
+
+bool RenderTarget::auto_viewport() const
+{
+ return auto_vport;
+}
+
+void RenderTarget::bind() const
+{
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ if(auto_vport) {
+ glViewport(0, 0, width, height);
+ }
+}
+
+const Mat4 &RenderTarget::texture_matrix() const
+{
+ return texmat;
+}
+
+static void attach_depth_texture(Texture *tex)
+{
+ unsigned int fmt = tex->get_format();
+ unsigned int attachment = 0;
+
+ switch(fmt) {
+ case GL_DEPTH_COMPONENT:
+ case GL_DEPTH_COMPONENT16:
+ case GL_DEPTH_COMPONENT24:
+ case GL_DEPTH_COMPONENT32:
+ attachment = GL_DEPTH_ATTACHMENT;
+ break;
+
+ case GL_STENCIL_INDEX:
+ case GL_STENCIL_INDEX1:
+ case GL_STENCIL_INDEX4:
+ case GL_STENCIL_INDEX8:
+ attachment = GL_STENCIL_ATTACHMENT;
+ break;
+
+ case GL_DEPTH_STENCIL:
+ case GL_DEPTH24_STENCIL8:
+ case GL_DEPTH32F_STENCIL8:
+ case GL_UNSIGNED_INT_24_8:
+ attachment = GL_DEPTH_STENCIL_ATTACHMENT;
+ break;
+
+ default:
+ error_log("failed to attach depth/stencil texture: unexpected texture format: %x\n", fmt);
+ break;
+ }
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, tex->get_id(), 0);
+}