diff --git a/lib/gl/renderer.hpp b/lib/gl/renderer.hpp index 3de73ff3..284532ea 100644 --- a/lib/gl/renderer.hpp +++ b/lib/gl/renderer.hpp @@ -104,6 +104,8 @@ class GLDevice std::array static_color; + float line_w; + protected: TextureHandle passthrough_texture; @@ -139,7 +141,7 @@ class GLDevice void disableBlend() { glDisable(GL_BLEND); } void enableDepthWrite() { glDepthMask(GL_TRUE); } void disableDepthWrite() { glDepthMask(GL_FALSE); } - void setLineWidth(float w) { glLineWidth(w); } + void setLineWidth(float w) { line_w = w; glLineWidth(w); } virtual void init(); virtual DeviceType getType() = 0; diff --git a/lib/gl/renderer_core.cpp b/lib/gl/renderer_core.cpp index ad3dee63..19ee0d3b 100644 --- a/lib/gl/renderer_core.cpp +++ b/lib/gl/renderer_core.cpp @@ -45,6 +45,9 @@ const std::vector CoreGLDevice::unif_list = "useClipPlane", "clipPlane", "containsText", + "expandLines", + "lineWidth", + "aspectRatio", "modelViewMatrix", "projectionMatrix", "textProjMatrix", @@ -66,6 +69,84 @@ const std::vector CoreGLDevice::unif_list = "alphaTex" }; +struct alignas(16) CoreGLDevice::LineVertex +{ + std::array vtx; + float orientation; + std::array prev; + std::array next; + + static constexpr array_layout layout = LAYOUT_EXT_LINE_VTX; + + static void Setup() + { + constexpr LineVertex base{}; + const size_t vtx_offset = (size_t)(&base.vtx) - (size_t)(&base); + const size_t orient_offset = (size_t)(&base.orientation) - (size_t)(&base); + const size_t prev_offset = (size_t)(&base.prev) - (size_t)(&base); + const size_t next_offset = (size_t)(&base.next) - (size_t)(&base); + + glEnableVertexAttribArray(ATTR_VERTEX); + glEnableVertexAttribArray(ATTR_LINE_ORIENT); + glEnableVertexAttribArray(ATTR_LINE_PREV); + glEnableVertexAttribArray(ATTR_LINE_NEXT); + + glVertexAttribPointer(ATTR_VERTEX, 3, GL_FLOAT, false, sizeof(LineVertex), (void*)vtx_offset); + glVertexAttribPointer(ATTR_LINE_ORIENT, 1, GL_FLOAT, false, sizeof(LineVertex), (void*)orient_offset); + glVertexAttribPointer(ATTR_LINE_PREV, 3, GL_FLOAT, false, sizeof(LineVertex), (void*)prev_offset); + glVertexAttribPointer(ATTR_LINE_NEXT, 3, GL_FLOAT, false, sizeof(LineVertex), (void*)next_offset); + } + + static void Finish() + { + glDisableVertexAttribArray(ATTR_VERTEX); + glDisableVertexAttribArray(ATTR_LINE_ORIENT); + glDisableVertexAttribArray(ATTR_LINE_PREV); + glDisableVertexAttribArray(ATTR_LINE_NEXT); + } +}; + +struct alignas(16) CoreGLDevice::LineColorVertex +{ + std::array vtx; + std::array color; + float orientation; + std::array prev; + std::array next; + + static constexpr array_layout layout = LAYOUT_EXT_LINE_VTX_COLOR; + + static void Setup() + { + constexpr LineColorVertex base{}; + const size_t vtx_offset = (size_t)(&base.vtx) - (size_t)(&base); + const size_t color_offset = (size_t)(&base.color) - (size_t)(&base); + const size_t orient_offset = (size_t)(&base.orientation) - (size_t)(&base); + const size_t prev_offset = (size_t)(&base.prev) - (size_t)(&base); + const size_t next_offset = (size_t)(&base.next) - (size_t)(&base); + glEnableVertexAttribArray(ATTR_VERTEX); + glEnableVertexAttribArray(ATTR_COLOR); + glEnableVertexAttribArray(ATTR_LINE_ORIENT); + glEnableVertexAttribArray(ATTR_LINE_PREV); + glEnableVertexAttribArray(ATTR_LINE_NEXT); + + glVertexAttribPointer(ATTR_VERTEX, 3, GL_FLOAT, false, sizeof(LineColorVertex), (void*)vtx_offset); + glVertexAttribPointer(ATTR_COLOR, 4, GL_UNSIGNED_INT, true, sizeof(LineColorVertex), (void*)color_offset); + glVertexAttribPointer(ATTR_LINE_ORIENT, 1, GL_FLOAT, false, sizeof(LineColorVertex), (void*)orient_offset); + glVertexAttribPointer(ATTR_LINE_PREV, 3, GL_FLOAT, false, sizeof(LineColorVertex), (void*)prev_offset); + glVertexAttribPointer(ATTR_LINE_NEXT, 3, GL_FLOAT, false, sizeof(LineColorVertex), (void*)next_offset); + } + + static void Finish() + { + glDisableVertexAttribArray(ATTR_VERTEX); + glDisableVertexAttribArray(ATTR_COLOR); + glDisableVertexAttribArray(ATTR_LINE_ORIENT); + glDisableVertexAttribArray(ATTR_LINE_PREV); + glDisableVertexAttribArray(ATTR_LINE_NEXT); + } +}; + template void setupVtxAttrLayout() { @@ -96,7 +177,10 @@ bool CoreGLDevice::compileShaders() { CoreGLDevice::ATTR_TEXT_VERTEX, "textVertex"}, { CoreGLDevice::ATTR_NORMAL, "normal"}, { CoreGLDevice::ATTR_COLOR, "color"}, - { CoreGLDevice::ATTR_TEXCOORD0, "texCoord0"} + { CoreGLDevice::ATTR_TEXCOORD0, "texCoord0"}, + { CoreGLDevice::ATTR_LINE_ORIENT, "line_orientation"}, + { CoreGLDevice::ATTR_LINE_PREV, "line_prev_vtx"}, + { CoreGLDevice::ATTR_LINE_NEXT, "line_next_vtx"} }; if (!default_prgm.create(DEFAULT_VS, DEFAULT_FS, attribMap, 1)) @@ -246,22 +330,128 @@ void CoreGLDevice::setClipPlaneEqn(const std::array &eqn) void CoreGLDevice::bufferToDevice(array_layout layout, IVertexBuffer &buf) { - if (buf.getHandle() == 0) + if (buf.getShape() == GL_LINES && + (layout == Vertex::layout || layout == VertexColor::layout)) { + if (buf.getHandle() == 0) + { + array_layout ext_layout; + if (layout == Vertex::layout) { ext_layout = LineVertex::layout; } + else if (layout == VertexColor::layout) { ext_layout = LineColorVertex::layout; } + if (buf.count() == 0) { return; } + GLuint handle[2]; + glGenBuffers(2, &handle[0]); + buf.setHandle(vbos.size()); + vbos.emplace_back(VBOData{handle[0], handle[1], GL_TRIANGLES, 0, ext_layout}); + } + glBindBuffer(GL_ARRAY_BUFFER, vbos[buf.getHandle()].vert_buf); + glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); if (buf.count() == 0) { return; } - GLuint handle; - glGenBuffers(1, &handle); - buf.setHandle(vbos.size()); - vbos.emplace_back(VBOData{handle, 0, buf.getShape(), buf.count(), layout}); + // Create extended vertex data for generating shader-expanded lines + if (layout == Vertex::layout) + { + std::vector ext_data(buf.count() * 2); + auto pts = static_cast&>(buf).begin(); + for (size_t i = 0; i < buf.count(); i++) + { + const Vertex& pt = *(pts + i); + ext_data[2*i].vtx = pt.coord; + ext_data[2*i].orientation = 1; + ext_data[2*i+1].vtx = pt.coord; + ext_data[2*i+1].orientation = -1; + if (i % 2 == 0) + { + // first node in the segment + ext_data[2*(i+1)].prev = pt.coord; + ext_data[2*(i+1) + 1].prev = pt.coord; + ext_data[2*i].prev = pt.coord; + ext_data[2*i+1].prev = pt.coord; + } + else + { + // last node in the segment + ext_data[2*(i-1)].next = pt.coord; + ext_data[2*(i-1) + 1].next = pt.coord; + ext_data[2*i].next = pt.coord; + ext_data[2*i+1].next = pt.coord; + } + } + glBufferData(GL_ARRAY_BUFFER, ext_data.size() * sizeof(LineVertex), + ext_data.data(), GL_STATIC_DRAW); + } + else if (layout == VertexColor::layout) + { + std::vector ext_data(buf.count() * 2); + auto pts = static_cast&>(buf).begin(); + for (size_t i = 0; i < buf.count(); i++) + { + const VertexColor& pt = *(pts + i); + ext_data[2*i].vtx = pt.coord; + ext_data[2*i].color = pt.color; + ext_data[2*i].orientation = 1; + ext_data[2*i+1].vtx = pt.coord; + ext_data[2*i+1].color = pt.color; + ext_data[2*i+1].orientation = -1; + if (i % 2 == 0) + { + // first node in the segment + ext_data[2*(i+1)].prev = pt.coord; + ext_data[2*(i+1) + 1].prev = pt.coord; + ext_data[2*i].prev = pt.coord; + ext_data[2*i+1].prev = pt.coord; + } + else + { + // last node in the segment + ext_data[2*(i-1)].next = pt.coord; + ext_data[2*(i-1) + 1].next = pt.coord; + ext_data[2*i].next = pt.coord; + ext_data[2*i+1].next = pt.coord; + } + } + glBufferData(GL_ARRAY_BUFFER, ext_data.size() * sizeof(LineColorVertex), + ext_data.data(), GL_STATIC_DRAW); + } + // Create indices to generate triangles for our lines + std::vector index_data(buf.count() * 3); + for (size_t it = 0; it < buf.count() / 2; it++) + { + index_data[6*it] = 4*it; + index_data[6*it+1] = 4*it+1; + index_data[6*it+2] = 4*it+2; + + index_data[6*it+3] = 4*it+1; + index_data[6*it+4] = 4*it+2; + index_data[6*it+5] = 4*it+3; + } // iterate over triangles + + // Upload index data to GPU + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[buf.getHandle()].elem_buf); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_data.size() * sizeof(int), + index_data.data(), GL_STATIC_DRAW); + + vbos[buf.getHandle()].count = index_data.size(); } else { - vbos[buf.getHandle()].count = buf.count(); + if (buf.getHandle() == 0) + { + if (buf.count() == 0) { return; } + GLuint handle; + glGenBuffers(1, &handle); + buf.setHandle(vbos.size()); + vbos.emplace_back(VBOData{handle, 0, buf.getShape(), buf.count(), layout}); + } + else + { + vbos[buf.getHandle()].count = buf.count(); + } + glBindBuffer(GL_ARRAY_BUFFER, vbos[buf.getHandle()].vert_buf); + glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, buf.count() * buf.getStride(), + buf.getData(), GL_STATIC_DRAW); } - glBindBuffer(GL_ARRAY_BUFFER, vbos[buf.getHandle()].vert_buf); - glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); - glBufferData(GL_ARRAY_BUFFER, buf.count() * buf.getStride(), - buf.getData(), GL_STATIC_DRAW); } void CoreGLDevice::bufferToDevice(array_layout layout, IIndexedBuffer& buf) @@ -360,6 +550,38 @@ void CoreGLDevice::drawDeviceBufferImpl(GLenum shape, int count, bool indexed) clearVtxAttrLayout(); } +void CoreGLDevice::drawExtendedLineImpl(array_layout type, int count) +{ + // Set polygon offset to pull lines towards camera + glPolygonOffset(0, 0); + // Set up uniforms + glUniform1i(uniforms["expandLines"], true); + glUniform1f(uniforms["lineWidth"], 2 * line_w / vp_width); + glUniform1f(uniforms["aspectRatio"], (float)vp_width / vp_height); + // Set up attributes + glVertexAttrib3f(CoreGLDevice::ATTR_NORMAL, 0.f, 0.f, 1.f); + if (type == LineVertex::layout) + { + LineVertex::Setup(); + } + else if (type == LineColorVertex::layout) + { + LineColorVertex::Setup(); + } + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_INT, (void*)0); + if (type == LineVertex::layout) + { + LineVertex::Finish(); + } + else if (type == LineColorVertex::layout) + { + LineColorVertex::Finish(); + } + glUniform1i(uniforms["expandLines"], false); + // Reset polygon offset to default value + glPolygonOffset(1, 1); +} + void CoreGLDevice::drawDeviceBuffer(int hnd) { if (hnd == 0) { return; } @@ -372,7 +594,8 @@ void CoreGLDevice::drawDeviceBuffer(int hnd) indexed = true; } if (vbos[hnd].layout == Vertex::layout - || vbos[hnd].layout == VertexNorm::layout) + || vbos[hnd].layout == VertexNorm::layout + || vbos[hnd].layout == LineVertex::layout) { glVertexAttrib4fv(ATTR_COLOR, static_color.data()); } @@ -398,6 +621,10 @@ void CoreGLDevice::drawDeviceBuffer(int hnd) case VertexNormTex::layout: drawDeviceBufferImpl(shape, count, indexed); break; + case LineVertex::layout: + case LineColorVertex::layout: + drawExtendedLineImpl(vbos[hnd].layout, count); + break; default: cerr << "WARNING: Unhandled vertex layout " << vbos[hnd].layout << endl; } diff --git a/lib/gl/renderer_core.hpp b/lib/gl/renderer_core.hpp index 75a0b3f9..a03651b1 100644 --- a/lib/gl/renderer_core.hpp +++ b/lib/gl/renderer_core.hpp @@ -27,6 +27,9 @@ class CoreGLDevice : public GLDevice ATTR_NORMAL, ATTR_COLOR, ATTR_TEXCOORD0, + ATTR_LINE_ORIENT, + ATTR_LINE_PREV, + ATTR_LINE_NEXT, NUM_ATTRS }; @@ -59,6 +62,9 @@ class CoreGLDevice : public GLDevice array_layout layout; }; + struct LineVertex; + struct LineColorVertex; + std::vector vbos; bool compileShaders(); @@ -67,6 +73,8 @@ class CoreGLDevice : public GLDevice template void drawDeviceBufferImpl(GLenum shape, int count, bool indexed); + void drawExtendedLineImpl(array_layout type, int count); + void processTriangleXfbBuffer(CaptureBuffer& cbuf, const vector& verts); void processLineXfbBuffer(CaptureBuffer& cbuf, diff --git a/lib/gl/shaders/default.vert b/lib/gl/shaders/default.vert index c33127fb..6701610a 100644 --- a/lib/gl/shaders/default.vert +++ b/lib/gl/shaders/default.vert @@ -16,8 +16,16 @@ attribute vec4 color; attribute vec3 normal; attribute vec2 texCoord0; +attribute float line_orientation; +attribute vec3 line_prev_vtx; +attribute vec3 line_next_vtx; + uniform bool containsText; +uniform bool expandLines; +uniform float lineWidth; +uniform float aspectRatio; + uniform mat4 modelViewMatrix; uniform mat4 projectionMatrix; uniform mat4 textProjMatrix; @@ -52,4 +60,49 @@ void main() vec4 textOffset = textProjMatrix * vec4(textVertex, 0.0, 0.0); gl_Position += vec4((textOffset.xy * pos.w), -0.005, 0.0); } + // Below code adapted from: + // https://github.com/mattdesl/webgl-lines + if (expandLines) + { + // Compute screen-space coordinates for current and previous segments + mat4 mvp = projectionMatrix * modelViewMatrix; + vec4 prev_clip = mvp * vec4(line_prev_vtx, 1.0); + vec4 next_clip = mvp * vec4(line_next_vtx, 1.0); + + vec2 curr_scrn = pos.xy / pos.w; + vec2 prev_scrn = prev_clip.xy / prev_clip.w; + vec2 next_scrn = next_clip.xy / next_clip.w; + + // Correct for current aspect ratio + curr_scrn.x *= aspectRatio; + prev_scrn.x *= aspectRatio; + next_scrn.x *= aspectRatio; + + float width = lineWidth; + + // Get direction and normal of line segment + vec2 dir; + if (vertex == line_prev_vtx) + { + dir = normalize(next_scrn - curr_scrn); + } + else if (vertex == line_next_vtx) + { + dir = normalize(curr_scrn - prev_scrn); + } + else + { + dir = normalize(curr_scrn - prev_scrn); + vec2 perp = vec2(-dir.y, dir.x); + + dir += normalize(next_scrn - curr_scrn); + dir = normalize(dir); + vec2 miter = vec2(-dir.y, dir.x); + width = lineWidth / dot(miter, perp); + } + vec2 line_normal = vec2(-dir.y, dir.x); + line_normal.x /= aspectRatio; + vec4 offset = vec4(line_normal * line_orientation * width * pos.w / 2.0, 0.0, 0.0); + gl_Position += offset; + } })" diff --git a/lib/gl/types.hpp b/lib/gl/types.hpp index fcdb9598..600fc79b 100644 --- a/lib/gl/types.hpp +++ b/lib/gl/types.hpp @@ -57,8 +57,7 @@ class Handle { if (this != &other) { - hnd = other.hnd; - other.hnd = 0; + std::swap(hnd, other.hnd); } return *this; } @@ -175,6 +174,8 @@ enum array_layout LAYOUT_VTX_TEXTURE0, LAYOUT_VTX_NORMAL_COLOR, LAYOUT_VTX_NORMAL_TEXTURE0, + LAYOUT_EXT_LINE_VTX, + LAYOUT_EXT_LINE_VTX_COLOR, NUM_LAYOUTS }; diff --git a/tests/data b/tests/data index 1fc29549..cb71613e 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 1fc29549fc8d76daea4d8354ec53ade80ce93d49 +Subproject commit cb71613e2149cb6586567d9dc5dd1f7a63132bea