// Copyright (C) 2017 Michael Zeilfelder // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in Irrlicht.h #include "CWebGL1Driver.h" #ifdef _IRR_COMPILE_WITH_WEBGL1_ #include "COpenGLCoreTexture.h" #include "COpenGLCoreRenderTarget.h" #include "COpenGLCoreCacheHandler.h" #include "EVertexAttributes.h" namespace irr { namespace video { CWebGL1Driver::CWebGL1Driver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, IContextManager* contextManager) : COGLES2Driver(params, io, contextManager) , MBTriangleFanSize4(0), MBLinesSize2(0), MBPointsSize1(0) { #ifdef _DEBUG setDebugName("CWebGL1Driver"); #endif // NPOT are not allowed for WebGL in most cases. // One can use them when: // - The TEXTURE_MIN_FILTER is linear or nearest // - no mipmapping is used // - no texture wrapping is used (so all texture_wraps have to be CLAMP_TO_EDGE) // So users could still enable them for specific cases (usually GUI), but in general better to have it off. disableFeature(EVDF_TEXTURE_NPOT); MBLinesSize2 = createSimpleMeshBuffer(2, scene::EPT_LINES); MBTriangleFanSize4 = createSimpleMeshBuffer(4, scene::EPT_TRIANGLE_FAN); MBPointsSize1 = createSimpleMeshBuffer(1, scene::EPT_POINTS); } CWebGL1Driver::~CWebGL1Driver() { if ( MBTriangleFanSize4 ) MBTriangleFanSize4->drop(); if ( MBLinesSize2 ) MBLinesSize2->drop(); if ( MBPointsSize1 ) MBPointsSize1->drop(); } //! Returns type of video driver E_DRIVER_TYPE CWebGL1Driver::getDriverType() const { return EDT_WEBGL1; } //! draws a vertex primitive list void CWebGL1Driver::drawVertexPrimitiveList(const void* vertices, u32 vertexCount, const void* indexList, u32 primitiveCount, E_VERTEX_TYPE vType, scene::E_PRIMITIVE_TYPE pType, E_INDEX_TYPE iType) { if ( !vertices ) { COGLES2Driver::drawVertexPrimitiveList(vertices, vertexCount, indexList, primitiveCount, vType, pType, iType); } else { static bool first = true; if ( first ) { first = false; os::Printer::log("WebGL driver does not support drawVertexPrimitiveList calls without a VBO", ELL_WARNING); os::Printer::log(__FILE__, irr::core::stringc(__LINE__).c_str(), ELL_WARNING); } } } //! Draws a mesh buffer void CWebGL1Driver::drawMeshBuffer(const scene::IMeshBuffer* mb) { if ( mb ) { // OK - this is bad and I hope I can find a better solution. // Basically casting away a const which shouldn't be cast away. // Not a nice surprise for users to see their mesh changes I guess :-( scene::IMeshBuffer* mbUglyHack = const_cast(mb); // We can't allow any buffers which are not bound to some VBO. if ( mb->getHardwareMappingHint_Vertex() == scene::EHM_NEVER) mbUglyHack->setHardwareMappingHint(scene::EHM_STREAM, scene::EBT_VERTEX); if ( mb->getHardwareMappingHint_Index() == scene::EHM_NEVER) mbUglyHack->setHardwareMappingHint(scene::EHM_STREAM, scene::EBT_INDEX); COGLES2Driver::drawMeshBuffer(mb); } } void CWebGL1Driver::draw2DImage(const video::ITexture* texture, const core::position2d& destPos,const core::rect& sourceRect, const core::rect* clipRect, SColor color, bool useAlphaChannelOfTexture) { if (!texture) return; if (!sourceRect.isValid()) return; core::position2d targetPos(destPos); core::position2d sourcePos(sourceRect.UpperLeftCorner); core::dimension2d sourceSize(sourceRect.getSize()); if (clipRect) { if (targetPos.X < clipRect->UpperLeftCorner.X) { sourceSize.Width += targetPos.X - clipRect->UpperLeftCorner.X; if (sourceSize.Width <= 0) return; sourcePos.X -= targetPos.X - clipRect->UpperLeftCorner.X; targetPos.X = clipRect->UpperLeftCorner.X; } if (targetPos.X + sourceSize.Width > clipRect->LowerRightCorner.X) { sourceSize.Width -= (targetPos.X + sourceSize.Width) - clipRect->LowerRightCorner.X; if (sourceSize.Width <= 0) return; } if (targetPos.Y < clipRect->UpperLeftCorner.Y) { sourceSize.Height += targetPos.Y - clipRect->UpperLeftCorner.Y; if (sourceSize.Height <= 0) return; sourcePos.Y -= targetPos.Y - clipRect->UpperLeftCorner.Y; targetPos.Y = clipRect->UpperLeftCorner.Y; } if (targetPos.Y + sourceSize.Height > clipRect->LowerRightCorner.Y) { sourceSize.Height -= (targetPos.Y + sourceSize.Height) - clipRect->LowerRightCorner.Y; if (sourceSize.Height <= 0) return; } } // clip these coordinates if (targetPos.X < 0) { sourceSize.Width += targetPos.X; if (sourceSize.Width <= 0) return; sourcePos.X -= targetPos.X; targetPos.X = 0; } const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); if (targetPos.X + sourceSize.Width > (s32)renderTargetSize.Width) { sourceSize.Width -= (targetPos.X + sourceSize.Width) - renderTargetSize.Width; if (sourceSize.Width <= 0) return; } if (targetPos.Y < 0) { sourceSize.Height += targetPos.Y; if (sourceSize.Height <= 0) return; sourcePos.Y -= targetPos.Y; targetPos.Y = 0; } if (targetPos.Y + sourceSize.Height > (s32)renderTargetSize.Height) { sourceSize.Height -= (targetPos.Y + sourceSize.Height) - renderTargetSize.Height; if (sourceSize.Height <= 0) return; } // ok, we've clipped everything. // now draw it. // texcoords need to be flipped horizontally for RTTs const bool isRTT = texture->isRenderTarget(); const core::dimension2d& ss = texture->getOriginalSize(); const f32 invW = 1.f / static_cast(ss.Width); const f32 invH = 1.f / static_cast(ss.Height); const core::rect tcoords( sourcePos.X * invW, (isRTT ? (sourcePos.Y + sourceSize.Height) : sourcePos.Y) * invH, (sourcePos.X + sourceSize.Width) * invW, (isRTT ? sourcePos.Y : (sourcePos.Y + sourceSize.Height)) * invH); const core::rect poss(targetPos, sourceSize); chooseMaterial2D(); if ( !setMaterialTexture(0, texture) ) return; setRenderStates2DMode(color.getAlpha() < 255, true, useAlphaChannelOfTexture); lockRenderStateMode(); f32 left = (f32)poss.UpperLeftCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 right = (f32)poss.LowerRightCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 down = 2.f - (f32)poss.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)poss.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBTriangleFanSize4->Vertices[0] = S3DVertex(left, top, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[1] = S3DVertex(right, top, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[2] = S3DVertex(right, down, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->Vertices[3] = S3DVertex(left, down, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); unlockRenderStateMode(); } void CWebGL1Driver::draw2DImage(const video::ITexture* texture, const core::rect& destRect, const core::rect& sourceRect, const core::rect* clipRect, const video::SColor* const colors, bool useAlphaChannelOfTexture) { if (!texture) return; // texcoords need to be flipped horizontally for RTTs const bool isRTT = texture->isRenderTarget(); const core::dimension2du& ss = texture->getOriginalSize(); const f32 invW = 1.f / static_cast(ss.Width); const f32 invH = 1.f / static_cast(ss.Height); const core::rect tcoords( sourceRect.UpperLeftCorner.X * invW, (isRTT ? sourceRect.LowerRightCorner.Y : sourceRect.UpperLeftCorner.Y) * invH, sourceRect.LowerRightCorner.X * invW, (isRTT ? sourceRect.UpperLeftCorner.Y : sourceRect.LowerRightCorner.Y) *invH); const video::SColor temp[4] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; const video::SColor* const useColor = colors ? colors : temp; chooseMaterial2D(); if ( !setMaterialTexture(0, texture) ) return; setRenderStates2DMode(useColor[0].getAlpha() < 255 || useColor[1].getAlpha() < 255 || useColor[2].getAlpha() < 255 || useColor[3].getAlpha() < 255, true, useAlphaChannelOfTexture); lockRenderStateMode(); const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); bool useScissorTest = false; if (clipRect && clipRect->isValid()) { useScissorTest = true; glEnable(GL_SCISSOR_TEST); glScissor(clipRect->UpperLeftCorner.X, renderTargetSize.Height - clipRect->LowerRightCorner.Y, clipRect->getWidth(), clipRect->getHeight()); } f32 left = (f32)destRect.UpperLeftCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 right = (f32)destRect.LowerRightCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 down = 2.f - (f32)destRect.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)destRect.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBTriangleFanSize4->Vertices[0] = S3DVertex(left, top, 0, 0, 0, 1, useColor[0], tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[1] = S3DVertex(right, top, 0, 0, 0, 1, useColor[3], tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[2] = S3DVertex(right, down, 0, 0, 0, 1, useColor[2], tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->Vertices[3] = S3DVertex(left, down, 0, 0, 0, 1, useColor[1], tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); if (useScissorTest) glDisable(GL_SCISSOR_TEST); unlockRenderStateMode(); testGLError(); } void CWebGL1Driver::draw2DImage(const video::ITexture* texture, u32 layer, bool flip) { if (!texture ) return; chooseMaterial2D(); if ( !setMaterialTexture(0, texture) ) return; setRenderStates2DMode(false, true, true); lockRenderStateMode(); MBTriangleFanSize4->Vertices[0].Pos = core::vector3df(-1.f, 1.f, 0.f); MBTriangleFanSize4->Vertices[1].Pos = core::vector3df(1.f, 1.f, 0.f); MBTriangleFanSize4->Vertices[2].Pos = core::vector3df(1.f, -1.f, 0.f); MBTriangleFanSize4->Vertices[3].Pos = core::vector3df(-1.f, -1.f, 0.f); f32 modificator = (flip) ? 1.f : 0.f; MBTriangleFanSize4->Vertices[0].TCoords = core::vector2df(0.f, 0.f + modificator); MBTriangleFanSize4->Vertices[1].TCoords = core::vector2df(1.f, 0.f + modificator); MBTriangleFanSize4->Vertices[2].TCoords = core::vector2df(1.f, 1.f - modificator); MBTriangleFanSize4->Vertices[3].TCoords = core::vector2df(0.f, 1.f - modificator); MBTriangleFanSize4->Vertices[0].Color = SColor(0xFFFFFFFF); MBTriangleFanSize4->Vertices[1].Color = SColor(0xFFFFFFFF); MBTriangleFanSize4->Vertices[2].Color = SColor(0xFFFFFFFF); MBTriangleFanSize4->Vertices[3].Color = SColor(0xFFFFFFFF); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); unlockRenderStateMode(); } void CWebGL1Driver::draw2DImageBatch(const video::ITexture* texture, const core::position2d& pos, const core::array >& sourceRects, const core::array& indices, s32 kerningWidth, const core::rect* clipRect, SColor color, bool useAlphaChannelOfTexture) { if (!texture) return; chooseMaterial2D(); if ( !setMaterialTexture(0, texture) ) return; setRenderStates2DMode(color.getAlpha() < 255, true, useAlphaChannelOfTexture); lockRenderStateMode(); const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); bool useScissorTest = false; if (clipRect && clipRect->isValid()) { useScissorTest = true; glEnable(GL_SCISSOR_TEST); glScissor(clipRect->UpperLeftCorner.X, renderTargetSize.Height - clipRect->LowerRightCorner.Y, clipRect->getWidth(), clipRect->getHeight()); } const core::dimension2du& ss = texture->getOriginalSize(); core::position2d targetPos(pos); // texcoords need to be flipped horizontally for RTTs const bool isRTT = texture->isRenderTarget(); const f32 invW = 1.f / static_cast(ss.Width); const f32 invH = 1.f / static_cast(ss.Height); for (u32 i = 0; i < indices.size(); ++i) { const s32 currentIndex = indices[i]; if (!sourceRects[currentIndex].isValid()) break; const core::rect tcoords( sourceRects[currentIndex].UpperLeftCorner.X * invW, (isRTT ? sourceRects[currentIndex].LowerRightCorner.Y : sourceRects[currentIndex].UpperLeftCorner.Y) * invH, sourceRects[currentIndex].LowerRightCorner.X * invW, (isRTT ? sourceRects[currentIndex].UpperLeftCorner.Y : sourceRects[currentIndex].LowerRightCorner.Y) * invH); const core::rect poss(targetPos, sourceRects[currentIndex].getSize()); f32 left = (f32)poss.UpperLeftCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 right = (f32)poss.LowerRightCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 down = 2.f - (f32)poss.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)poss.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBTriangleFanSize4->Vertices[0] = S3DVertex(left, top, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[1] = S3DVertex(right, top, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[2] = S3DVertex(right, down, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->Vertices[3] = S3DVertex(left, down, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); targetPos.X += sourceRects[currentIndex].getWidth(); } if (useScissorTest) glDisable(GL_SCISSOR_TEST); unlockRenderStateMode(); testGLError(); } void CWebGL1Driver::draw2DImageBatch(const video::ITexture* texture, const core::array >& positions, const core::array >& sourceRects, const core::rect* clipRect, SColor color, bool useAlphaChannelOfTexture) { if (!texture) return; const irr::u32 drawCount = core::min_(positions.size(), sourceRects.size()); if ( !drawCount ) return; chooseMaterial2D(); if ( !setMaterialTexture(0, texture) ) return; setRenderStates2DMode(color.getAlpha() < 255, true, useAlphaChannelOfTexture); lockRenderStateMode(); for (u32 i = 0; i < drawCount; i++) { core::position2d targetPos = positions[i]; core::position2d sourcePos = sourceRects[i].UpperLeftCorner; // This needs to be signed as it may go negative. core::dimension2d sourceSize(sourceRects[i].getSize()); if (clipRect) { if (targetPos.X < clipRect->UpperLeftCorner.X) { sourceSize.Width += targetPos.X - clipRect->UpperLeftCorner.X; if (sourceSize.Width <= 0) continue; sourcePos.X -= targetPos.X - clipRect->UpperLeftCorner.X; targetPos.X = clipRect->UpperLeftCorner.X; } if (targetPos.X + (s32)sourceSize.Width > clipRect->LowerRightCorner.X) { sourceSize.Width -= (targetPos.X + sourceSize.Width) - clipRect->LowerRightCorner.X; if (sourceSize.Width <= 0) continue; } if (targetPos.Y < clipRect->UpperLeftCorner.Y) { sourceSize.Height += targetPos.Y - clipRect->UpperLeftCorner.Y; if (sourceSize.Height <= 0) continue; sourcePos.Y -= targetPos.Y - clipRect->UpperLeftCorner.Y; targetPos.Y = clipRect->UpperLeftCorner.Y; } if (targetPos.Y + (s32)sourceSize.Height > clipRect->LowerRightCorner.Y) { sourceSize.Height -= (targetPos.Y + sourceSize.Height) - clipRect->LowerRightCorner.Y; if (sourceSize.Height <= 0) continue; } } // clip these coordinates if (targetPos.X < 0) { sourceSize.Width += targetPos.X; if (sourceSize.Width <= 0) continue; sourcePos.X -= targetPos.X; targetPos.X = 0; } const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); if (targetPos.X + sourceSize.Width > (s32)renderTargetSize.Width) { sourceSize.Width -= (targetPos.X + sourceSize.Width) - renderTargetSize.Width; if (sourceSize.Width <= 0) continue; } if (targetPos.Y < 0) { sourceSize.Height += targetPos.Y; if (sourceSize.Height <= 0) continue; sourcePos.Y -= targetPos.Y; targetPos.Y = 0; } if (targetPos.Y + sourceSize.Height > (s32)renderTargetSize.Height) { sourceSize.Height -= (targetPos.Y + sourceSize.Height) - renderTargetSize.Height; if (sourceSize.Height <= 0) continue; } // ok, we've clipped everything. // now draw it. core::rect tcoords; tcoords.UpperLeftCorner.X = (((f32)sourcePos.X)) / texture->getOriginalSize().Width ; tcoords.UpperLeftCorner.Y = (((f32)sourcePos.Y)) / texture->getOriginalSize().Height; tcoords.LowerRightCorner.X = tcoords.UpperLeftCorner.X + ((f32)(sourceSize.Width) / texture->getOriginalSize().Width); tcoords.LowerRightCorner.Y = tcoords.UpperLeftCorner.Y + ((f32)(sourceSize.Height) / texture->getOriginalSize().Height); const core::rect poss(targetPos, sourceSize); f32 left = (f32)poss.UpperLeftCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 right = (f32)poss.LowerRightCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 down = 2.f - (f32)poss.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)poss.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBTriangleFanSize4->Vertices[0] = S3DVertex(left, top, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[1] = S3DVertex(right, top, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.UpperLeftCorner.Y); MBTriangleFanSize4->Vertices[2] = S3DVertex(right, down, 0, 0, 0, 1, color, tcoords.LowerRightCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->Vertices[3] = S3DVertex(left, down, 0, 0, 0, 1, color, tcoords.UpperLeftCorner.X, tcoords.LowerRightCorner.Y); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); } unlockRenderStateMode(); } //! draw a 2d rectangle void CWebGL1Driver::draw2DRectangle(SColor color, const core::rect& position, const core::rect* clip) { chooseMaterial2D(); setMaterialTexture(0, 0); setRenderStates2DMode(color.getAlpha() < 255, false, false); lockRenderStateMode(); core::rect pos = position; if (clip) pos.clipAgainst(*clip); if (!pos.isValid()) return; const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); f32 left = (f32)pos.UpperLeftCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 right = (f32)pos.LowerRightCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 down = 2.f - (f32)pos.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)pos.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBTriangleFanSize4->Vertices[0] = S3DVertex(left, top, 0, 0, 0, 1, color, 0, 0); MBTriangleFanSize4->Vertices[1] = S3DVertex(right, top, 0, 0, 0, 1, color, 0, 0); MBTriangleFanSize4->Vertices[2] = S3DVertex(right, down, 0, 0, 0, 1, color, 0, 0); MBTriangleFanSize4->Vertices[3] = S3DVertex(left, down, 0, 0, 0, 1, color, 0, 0); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); unlockRenderStateMode(); } void CWebGL1Driver::draw2DRectangle(const core::rect& position, SColor colorLeftUp, SColor colorRightUp, SColor colorLeftDown, SColor colorRightDown, const core::rect* clip) { core::rect pos = position; if (clip) pos.clipAgainst(*clip); if (!pos.isValid()) return; chooseMaterial2D(); setMaterialTexture(0, 0); setRenderStates2DMode(colorLeftUp.getAlpha() < 255 || colorRightUp.getAlpha() < 255 || colorLeftDown.getAlpha() < 255 || colorRightDown.getAlpha() < 255, false, false); lockRenderStateMode(); const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); f32 left = (f32)pos.UpperLeftCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 right = (f32)pos.LowerRightCorner.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 down = 2.f - (f32)pos.LowerRightCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 top = 2.f - (f32)pos.UpperLeftCorner.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBTriangleFanSize4->Vertices[0] = S3DVertex(left, top, 0, 0, 0, 1, colorLeftUp, 0, 0); MBTriangleFanSize4->Vertices[1] = S3DVertex(right, top, 0, 0, 0, 1, colorRightUp, 0, 0); MBTriangleFanSize4->Vertices[2] = S3DVertex(right, down, 0, 0, 0, 1, colorRightDown, 0, 0); MBTriangleFanSize4->Vertices[3] = S3DVertex(left, down, 0, 0, 0, 1, colorLeftDown, 0, 0); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); unlockRenderStateMode(); } //! Draws a 2d line. void CWebGL1Driver::draw2DLine(const core::position2d& start, const core::position2d& end, SColor color) { if (start==end) drawPixel(start.X, start.Y, color); else { chooseMaterial2D(); setMaterialTexture(0, 0); setRenderStates2DMode(color.getAlpha() < 255, false, false); lockRenderStateMode(); const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); f32 startX = (f32)start.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 endX = (f32)end.X / (f32)renderTargetSize.Width * 2.f - 1.f; f32 startY = 2.f - (f32)start.Y / (f32)renderTargetSize.Height * 2.f - 1.f; f32 endY = 2.f - (f32)end.Y / (f32)renderTargetSize.Height * 2.f - 1.f; MBLinesSize2->Vertices[0] = S3DVertex(startX, startY, 0, 0, 0, 1, color, 0, 0); MBLinesSize2->Vertices[1] = S3DVertex(endX, endY, 0, 0, 0, 1, color, 1, 1); MBLinesSize2->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBLinesSize2); unlockRenderStateMode(); } } void CWebGL1Driver::drawPixel(u32 x, u32 y, const SColor & color) { const core::dimension2d& renderTargetSize = getCurrentRenderTargetSize(); if (x > (u32)renderTargetSize.Width || y > (u32)renderTargetSize.Height) return; chooseMaterial2D(); setMaterialTexture(0, 0); setRenderStates2DMode(color.getAlpha() < 255, false, false); lockRenderStateMode(); f32 X = (f32)x / (f32)renderTargetSize.Width * 2.f - 1.f; f32 Y = 2.f - (f32)y / (f32)renderTargetSize.Height * 2.f - 1.f; MBPointsSize1->Vertices[0] = S3DVertex(X, Y, 0, 0, 0, 1, color, 0, 0); MBPointsSize1->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBPointsSize1); unlockRenderStateMode(); } void CWebGL1Driver::draw3DLine(const core::vector3df& start, const core::vector3df& end, SColor color) { MBLinesSize2->Vertices[0] = S3DVertex(start.X, start.Y, start.Z, 0, 0, 1, color, 0, 0); MBLinesSize2->Vertices[1] = S3DVertex(end.X, end.Y, end.Z, 0, 0, 1, color, 0, 0); MBLinesSize2->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBLinesSize2); } void CWebGL1Driver::drawStencilShadowVolume(const core::array& triangles, bool zfail, u32 debugDataVisible) { static bool first = true; if ( first ) { first = false; os::Printer::log("WebGL1 driver does not yet support drawStencilShadowVolume", ELL_WARNING); os::Printer::log(__FILE__, irr::core::stringc(__LINE__).c_str(), ELL_WARNING); } } void CWebGL1Driver::drawStencilShadow(bool clearStencilBuffer, video::SColor leftUpEdge, video::SColor rightUpEdge, video::SColor leftDownEdge, video::SColor rightDownEdge) { // NOTE: Might work, but untested as drawStencilShadowVolume is not yet supported. if (!StencilBuffer) return; chooseMaterial2D(); setMaterialTexture(0, 0); setRenderStates2DMode(true, false, false); lockRenderStateMode(); CacheHandler->setDepthMask(false); CacheHandler->setColorMask(ECP_ALL); CacheHandler->setBlend(true); CacheHandler->setBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 0, ~0); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); MBTriangleFanSize4->Vertices[0] = S3DVertex(-1.f, 1.f, 0.9f, 0, 0, 1, leftDownEdge, 0, 0); MBTriangleFanSize4->Vertices[1] = S3DVertex(1.f, 1.f, 0.9f, 0, 0, 1, leftUpEdge, 0, 0); MBTriangleFanSize4->Vertices[2] = S3DVertex(1.f, -1.f, 0.9f, 0, 0, 1, rightUpEdge, 0, 0); MBTriangleFanSize4->Vertices[3] = S3DVertex(-1.f, -1.f, 0.9f, 0, 0, 1, rightDownEdge, 0, 0); MBTriangleFanSize4->setDirty(scene::EBT_VERTEX); drawMeshBuffer(MBTriangleFanSize4); unlockRenderStateMode(); if (clearStencilBuffer) glClear(GL_STENCIL_BUFFER_BIT); glDisable(GL_STENCIL_TEST); } GLenum CWebGL1Driver::getZBufferBits() const { // TODO: Never used, so not sure what this was really about (zbuffer used by device? Or for RTT's?) // If it's about device it might need a check like: GLint depthBits; glGetIntegerv(GL_DEPTH_BITS, &depthBits); // If it's about textures it might need a check for IRR_WEBGL_depth_texture GLenum bits = 0; switch (Params.ZBufferBits) { #if defined(GL_OES_depth24) case 24: bits = GL_DEPTH_COMPONENT24_OES; break; #endif #if defined(GL_OES_depth32) case 32: bits = GL_DEPTH_COMPONENT32_OES; break; #endif default: bits = GL_DEPTH_COMPONENT16_OES; break; } return bits; } bool CWebGL1Driver::getColorFormatParameters(ECOLOR_FORMAT format, GLint& internalFormat, GLenum& pixelFormat, GLenum& pixelType, void(**converter)(const void*, s32, void*)) const { bool supported = false; pixelFormat = GL_RGBA; pixelType = GL_UNSIGNED_BYTE; *converter = 0; switch (format) { case ECF_A1R5G5B5: supported = true; pixelFormat = GL_RGBA; pixelType = GL_UNSIGNED_SHORT_5_5_5_1; *converter = CColorConverter::convert_A1R5G5B5toR5G5B5A1; break; case ECF_R5G6B5: supported = true; pixelFormat = GL_RGB; pixelType = GL_UNSIGNED_SHORT_5_6_5; break; case ECF_R8G8B8: supported = true; pixelFormat = GL_RGB; pixelType = GL_UNSIGNED_BYTE; break; case ECF_A8R8G8B8: // WebGL doesn't seem to support GL_BGRA so we always convert supported = true; pixelFormat = GL_RGBA; *converter = CColorConverter::convert_A8R8G8B8toA8B8G8R8; pixelType = GL_UNSIGNED_BYTE; break; #ifdef GL_EXT_texture_compression_dxt1 case ECF_DXT1: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_s3tc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; } break; #endif #ifdef GL_EXT_texture_compression_s3tc case ECF_DXT2: case ECF_DXT3: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_s3tc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; } break; #endif #ifdef GL_EXT_texture_compression_s3tc case ECF_DXT4: case ECF_DXT5: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_s3tc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } break; #endif #ifdef GL_IMG_texture_compression_pvrtc case ECF_PVRTC_RGB2: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_pvrtc) ) { supported = true; pixelFormat = GL_RGB; pixelType = GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG; } break; #endif #ifdef GL_IMG_texture_compression_pvrtc case ECF_PVRTC_ARGB2: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_pvrtc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; } break; #endif #ifdef GL_IMG_texture_compression_pvrtc case ECF_PVRTC_RGB4: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_pvrtc) ) { supported = true; pixelFormat = GL_RGB; pixelType = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; } break; #endif #ifdef GL_IMG_texture_compression_pvrtc case ECF_PVRTC_ARGB4: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_pvrtc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; } break; #endif #ifdef GL_IMG_texture_compression_pvrtc2 case ECF_PVRTC2_ARGB2: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_pvrtc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG; } break; #endif #ifdef GL_IMG_texture_compression_pvrtc2 case ECF_PVRTC2_ARGB4: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_pvrtc) ) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG; } break; #endif #ifdef GL_OES_compressed_ETC1_RGB8_texture case ECF_ETC1: if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_compressed_texture_etc1) ) { supported = true; pixelFormat = GL_RGB; pixelType = GL_ETC1_RGB8_OES; } break; #endif #ifdef GL_ES_VERSION_3_0 // TO-DO - fix when extension name will be available case ECF_ETC2_RGB: supported = true; pixelFormat = GL_RGB; pixelType = GL_COMPRESSED_RGB8_ETC2; break; #endif #ifdef GL_ES_VERSION_3_0 // TO-DO - fix when extension name will be available case ECF_ETC2_ARGB: supported = true; pixelFormat = GL_RGBA; pixelType = GL_COMPRESSED_RGBA8_ETC2_EAC; break; #endif case ECF_D16: if (WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_depth_texture)) { supported = true; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_SHORT; } break; case ECF_D32: if (WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_depth_texture)) { // NOTE: There is still no guarantee it will return a 32 bit depth buffer. It might convert stuff internally to 16 bit :-( supported = true; pixelFormat = GL_DEPTH_COMPONENT; pixelType = GL_UNSIGNED_INT; } break; case ECF_D24S8: if (WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_WEBGL_depth_texture)) { supported = true; pixelFormat = 0x84F9; // GL_DEPTH_STENCIL pixelType = 0x84FA; // UNSIGNED_INT_24_8_WEBGL } break; case ECF_R8: // Does not seem to be supported in WebGL so far (missing GL_EXT_texture_rg) break; case ECF_R8G8: // Does not seem to be supported in WebGL so far (missing GL_EXT_texture_rg) break; case ECF_R16: // Does not seem to be supported in WebGL so far break; case ECF_R16G16: // Does not seem to be supported in WebGL so far break; case ECF_R16F: // Does not seem to be supported in WebGL so far (missing GL_EXT_texture_rg) break; case ECF_G16R16F: // Does not seem to be supported in WebGL so far (missing GL_EXT_texture_rg) break; case ECF_A16B16G16R16F: #if defined(GL_OES_texture_half_float) if (WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_OES_texture_half_float)) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_HALF_FLOAT_OES ; } #endif break; case ECF_R32F: // Does not seem to be supported in WebGL so far (missing GL_EXT_texture_rg) break; case ECF_G32R32F: // Does not seem to be supported in WebGL so far (missing GL_EXT_texture_rg) break; case ECF_A32B32G32R32F: #if defined(GL_OES_texture_float) if (WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_OES_texture_half_float)) { supported = true; pixelFormat = GL_RGBA; pixelType = GL_FLOAT ; } #endif break; default: break; } // ES 2.0 says internalFormat must match pixelFormat (chapter 3.7.1 in Spec). // Doesn't mention if "match" means "equal" or some other way of matching, but // some bug on Emscripten and browsing discussions by others lead me to believe // it means they have to be equal. Note that this was different in OpenGL. internalFormat = pixelFormat; return supported; } scene::SMeshBuffer* CWebGL1Driver::createSimpleMeshBuffer(irr::u32 numVertices, scene::E_PRIMITIVE_TYPE primitiveType, scene::E_HARDWARE_MAPPING vertexMappingHint, scene::E_HARDWARE_MAPPING indexMappingHint) const { scene::SMeshBuffer* mbResult = new scene::SMeshBuffer(); mbResult->Vertices.set_used(numVertices); mbResult->Indices.set_used(numVertices); for ( irr::u32 i=0; i < numVertices; ++i ) mbResult->Indices[i] = i; mbResult->setPrimitiveType(primitiveType); mbResult->setHardwareMappingHint(vertexMappingHint, scene::EBT_VERTEX); mbResult->setHardwareMappingHint(indexMappingHint, scene::EBT_INDEX); mbResult->setDirty(); return mbResult; } bool CWebGL1Driver::genericDriverInit(const core::dimension2d& screenSize, bool stencilBuffer) { Name = glGetString(GL_VERSION); printVersion(); // print renderer information VendorName = glGetString(GL_VENDOR); os::Printer::log(VendorName.c_str(), ELL_INFORMATION); // load extensions initWebGLExtensions(); // reset cache handler delete CacheHandler; CacheHandler = new COGLES2CacheHandler(this); StencilBuffer = stencilBuffer; DriverAttributes->setAttribute("MaxTextures", (s32)Feature.MaxTextureUnits); DriverAttributes->setAttribute("MaxSupportedTextures", (s32)Feature.MaxTextureUnits); DriverAttributes->setAttribute("MaxAnisotropy", MaxAnisotropy); DriverAttributes->setAttribute("MaxIndices", (s32)MaxIndices); DriverAttributes->setAttribute("MaxTextureSize", (s32)MaxTextureSize); DriverAttributes->setAttribute("MaxTextureLODBias", MaxTextureLODBias); DriverAttributes->setAttribute("Version", Version); DriverAttributes->setAttribute("AntiAlias", AntiAlias); glPixelStorei(GL_PACK_ALIGNMENT, 1); UserClipPlane.reallocate(0); for (s32 i = 0; i < ETS_COUNT; ++i) setTransform(static_cast(i), core::IdentityMatrix); setAmbientLight(SColorf(0.0f, 0.0f, 0.0f, 0.0f)); glClearDepthf(1.0f); glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); glFrontFace(GL_CW); // create material renderers createMaterialRenderers(); // set the renderstates setRenderStates3DMode(); // set fog mode setFog(FogColor, FogType, FogStart, FogEnd, FogDensity, PixelFog, RangeFog); // create matrix for flipping textures TextureFlipMatrix.buildTextureTransform(0.0f, core::vector2df(0, 0), core::vector2df(0, 1.0f), core::vector2df(1.0f, -1.0f)); // We need to reset once more at the beginning of the first rendering. // This fixes problems with intermediate changes to the material during texture load. ResetRenderStates = true; testGLError(__LINE__); return true; } void CWebGL1Driver::initWebGLExtensions() { // Stuff still a little bit hacky as we derive from ES2Driver with it's own extensions. // We only get the feature-strings from WebGLExtensions. getGLVersion(); WebGLExtensions.getGLExtensions(); // TODO: basically copied ES2 implementation, so not certain if 100% correct for WebGL GLint val=0; glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &val); Feature.MaxTextureUnits = static_cast(val); #ifdef GL_EXT_texture_filter_anisotropic if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_EXT_texture_filter_anisotropic) ) { glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &val); MaxAnisotropy = static_cast(val); } #endif if ( WebGLExtensions.queryWebGLFeature(CWebGLExtensionHandler::IRR_OES_element_index_uint) ) // note: WebGL2 won't need extension as that got default there { MaxIndices=0xffffffff; } glGetIntegerv(GL_MAX_TEXTURE_SIZE, &val); MaxTextureSize=static_cast(val); #ifdef GL_MAX_TEXTURE_LOD_BIAS_EXT // TODO: Found no info about this anywhere. It's no extension in WebGL // and GL_MAX_TEXTURE_LOD_BIAS_EXT doesn't seem to be part of gl2ext.h in emscripten glGetFloatv(GL_MAX_TEXTURE_LOD_BIAS_EXT, &MaxTextureLODBias); #endif glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, DimAliasedLine); glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, DimAliasedPoint); Feature.MaxTextureUnits = core::min_(Feature.MaxTextureUnits, static_cast(MATERIAL_MAX_TEXTURES)); Feature.ColorAttachment = 1; } } // end namespace video } // end namespace irr #endif // _IRR_COMPILE_WITH_WEBGL1_ namespace irr { #ifndef _IRR_COMPILE_WITH_WEBGL1_ namespace io { class IFileSystem; } #endif namespace video { #ifndef _IRR_COMPILE_WITH_WEBGL1_ class IVideoDriver; class IContextManager; #endif IVideoDriver* createWebGL1Driver(const SIrrlichtCreationParameters& params, io::IFileSystem* io, IContextManager* contextManager) { #ifdef _IRR_COMPILE_WITH_WEBGL1_ CWebGL1Driver* driver = new CWebGL1Driver(params, io, contextManager); driver->genericDriverInit(params.WindowSize, params.Stencilbuffer); // don't call in constructor, it uses virtual function calls of driver return driver; #else return 0; #endif // _IRR_COMPILE_WITH_WEBGL1_ } } // end namespace } // end namespace