context_js.go (18718B)
1 // Copyright 2014 Hajime Hoshi 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package opengl 16 17 import ( 18 "errors" 19 "fmt" 20 "syscall/js" 21 22 "github.com/hajimehoshi/ebiten/v2/internal/driver" 23 "github.com/hajimehoshi/ebiten/v2/internal/jsutil" 24 "github.com/hajimehoshi/ebiten/v2/internal/shaderir" 25 "github.com/hajimehoshi/ebiten/v2/internal/web" 26 ) 27 28 type ( 29 textureNative js.Value 30 framebufferNative js.Value 31 shader js.Value 32 buffer js.Value 33 uniformLocation js.Value 34 35 attribLocation int 36 programID int 37 program struct { 38 value js.Value 39 id programID 40 } 41 ) 42 43 func (t textureNative) equal(rhs textureNative) bool { 44 return jsutil.Equal(js.Value(t), js.Value(rhs)) 45 } 46 47 func (f framebufferNative) equal(rhs framebufferNative) bool { 48 return jsutil.Equal(js.Value(f), js.Value(rhs)) 49 } 50 51 func (s shader) equal(rhs shader) bool { 52 return jsutil.Equal(js.Value(s), js.Value(rhs)) 53 } 54 55 func (b buffer) equal(rhs buffer) bool { 56 return jsutil.Equal(js.Value(b), js.Value(rhs)) 57 } 58 59 func (u uniformLocation) equal(rhs uniformLocation) bool { 60 return jsutil.Equal(js.Value(u), js.Value(rhs)) 61 } 62 63 func (p program) equal(rhs program) bool { 64 return jsutil.Equal(p.value, rhs.value) && p.id == rhs.id 65 } 66 67 var InvalidTexture = textureNative(js.Null()) 68 69 var invalidUniform = uniformLocation(js.Null()) 70 71 func getProgramID(p program) programID { 72 return p.id 73 } 74 75 var ( 76 vertexShader shaderType 77 fragmentShader shaderType 78 arrayBuffer bufferType 79 elementArrayBuffer bufferType 80 dynamicDraw bufferUsage 81 streamDraw bufferUsage 82 pixelUnpackBuffer bufferType 83 short dataType 84 float dataType 85 86 zero operation 87 one operation 88 srcAlpha operation 89 dstAlpha operation 90 oneMinusSrcAlpha operation 91 oneMinusDstAlpha operation 92 dstColor operation 93 94 blend js.Value 95 clampToEdge js.Value 96 compileStatus js.Value 97 colorAttachment0 js.Value 98 framebuffer_ js.Value 99 framebufferBinding js.Value 100 framebufferComplete js.Value 101 highFloat js.Value 102 linkStatus js.Value 103 maxTextureSize js.Value 104 nearest js.Value 105 noError js.Value 106 rgba js.Value 107 texture2d js.Value 108 textureMagFilter js.Value 109 textureMinFilter js.Value 110 textureWrapS js.Value 111 textureWrapT js.Value 112 triangles js.Value 113 unpackAlignment js.Value 114 unsignedByte js.Value 115 unsignedShort js.Value 116 117 texture0 int 118 119 isWebGL2Available bool 120 ) 121 122 func init() { 123 // Accessing the prototype is rquired on Safari. 124 var contextPrototype js.Value 125 if !jsutil.Equal(js.Global().Get("WebGL2RenderingContext"), js.Undefined()) { 126 contextPrototype = js.Global().Get("WebGL2RenderingContext").Get("prototype") 127 isWebGL2Available = true 128 } else { 129 contextPrototype = js.Global().Get("WebGLRenderingContext").Get("prototype") 130 } 131 132 vertexShader = shaderType(contextPrototype.Get("VERTEX_SHADER").Int()) 133 fragmentShader = shaderType(contextPrototype.Get("FRAGMENT_SHADER").Int()) 134 arrayBuffer = bufferType(contextPrototype.Get("ARRAY_BUFFER").Int()) 135 elementArrayBuffer = bufferType(contextPrototype.Get("ELEMENT_ARRAY_BUFFER").Int()) 136 dynamicDraw = bufferUsage(contextPrototype.Get("DYNAMIC_DRAW").Int()) 137 streamDraw = bufferUsage(contextPrototype.Get("STREAM_DRAW").Int()) 138 short = dataType(contextPrototype.Get("SHORT").Int()) 139 float = dataType(contextPrototype.Get("FLOAT").Int()) 140 141 zero = operation(contextPrototype.Get("ZERO").Int()) 142 one = operation(contextPrototype.Get("ONE").Int()) 143 srcAlpha = operation(contextPrototype.Get("SRC_ALPHA").Int()) 144 dstAlpha = operation(contextPrototype.Get("DST_ALPHA").Int()) 145 oneMinusSrcAlpha = operation(contextPrototype.Get("ONE_MINUS_SRC_ALPHA").Int()) 146 oneMinusDstAlpha = operation(contextPrototype.Get("ONE_MINUS_DST_ALPHA").Int()) 147 dstColor = operation(contextPrototype.Get("DST_COLOR").Int()) 148 149 blend = contextPrototype.Get("BLEND") 150 clampToEdge = contextPrototype.Get("CLAMP_TO_EDGE") 151 compileStatus = contextPrototype.Get("COMPILE_STATUS") 152 colorAttachment0 = contextPrototype.Get("COLOR_ATTACHMENT0") 153 framebuffer_ = contextPrototype.Get("FRAMEBUFFER") 154 framebufferBinding = contextPrototype.Get("FRAMEBUFFER_BINDING") 155 framebufferComplete = contextPrototype.Get("FRAMEBUFFER_COMPLETE") 156 highFloat = contextPrototype.Get("HIGH_FLOAT") 157 linkStatus = contextPrototype.Get("LINK_STATUS") 158 maxTextureSize = contextPrototype.Get("MAX_TEXTURE_SIZE") 159 nearest = contextPrototype.Get("NEAREST") 160 noError = contextPrototype.Get("NO_ERROR") 161 rgba = contextPrototype.Get("RGBA") 162 texture0 = contextPrototype.Get("TEXTURE0").Int() 163 texture2d = contextPrototype.Get("TEXTURE_2D") 164 textureMagFilter = contextPrototype.Get("TEXTURE_MAG_FILTER") 165 textureMinFilter = contextPrototype.Get("TEXTURE_MIN_FILTER") 166 textureWrapS = contextPrototype.Get("TEXTURE_WRAP_S") 167 textureWrapT = contextPrototype.Get("TEXTURE_WRAP_T") 168 triangles = contextPrototype.Get("TRIANGLES") 169 unpackAlignment = contextPrototype.Get("UNPACK_ALIGNMENT") 170 unsignedByte = contextPrototype.Get("UNSIGNED_BYTE") 171 unsignedShort = contextPrototype.Get("UNSIGNED_SHORT") 172 173 if isWebGL2Available { 174 pixelUnpackBuffer = bufferType(contextPrototype.Get("PIXEL_UNPACK_BUFFER").Int()) 175 } 176 } 177 178 type contextImpl struct { 179 gl js.Value 180 lastProgramID programID 181 } 182 183 func (c *context) ensureGL() { 184 if !jsutil.Equal(c.gl, js.Value{}) { 185 return 186 } 187 188 // TODO: Define id? 189 canvas := js.Global().Get("document").Call("querySelector", "canvas") 190 attr := js.Global().Get("Object").New() 191 attr.Set("alpha", true) 192 attr.Set("premultipliedAlpha", true) 193 194 var gl js.Value 195 if isWebGL2Available { 196 gl = canvas.Call("getContext", "webgl2", attr) 197 } else { 198 gl = canvas.Call("getContext", "webgl", attr) 199 if jsutil.Equal(gl, js.Null()) { 200 gl = canvas.Call("getContext", "experimental-webgl", attr) 201 if jsutil.Equal(gl, js.Null()) { 202 panic("opengl: getContext failed") 203 } 204 } 205 } 206 207 c.gl = gl 208 } 209 210 func (c *context) reset() error { 211 c.locationCache = newLocationCache() 212 c.lastTexture = textureNative(js.Null()) 213 c.lastFramebuffer = framebufferNative(js.Null()) 214 c.lastViewportWidth = 0 215 c.lastViewportHeight = 0 216 c.lastCompositeMode = driver.CompositeModeUnknown 217 218 c.gl = js.Value{} 219 c.ensureGL() 220 if c.gl.Call("isContextLost").Bool() { 221 return driver.GraphicsNotReady 222 } 223 gl := c.gl 224 gl.Call("enable", blend) 225 c.blendFunc(driver.CompositeModeSourceOver) 226 f := gl.Call("getParameter", framebufferBinding) 227 c.screenFramebuffer = framebufferNative(f) 228 return nil 229 } 230 231 func (c *context) blendFunc(mode driver.CompositeMode) { 232 if c.lastCompositeMode == mode { 233 return 234 } 235 c.lastCompositeMode = mode 236 s, d := mode.Operations() 237 s2, d2 := convertOperation(s), convertOperation(d) 238 c.ensureGL() 239 gl := c.gl 240 gl.Call("blendFunc", int(s2), int(d2)) 241 } 242 243 func (c *context) newTexture(width, height int) (textureNative, error) { 244 c.ensureGL() 245 gl := c.gl 246 t := gl.Call("createTexture") 247 if jsutil.Equal(t, js.Null()) { 248 return textureNative(js.Null()), errors.New("opengl: glGenTexture failed") 249 } 250 gl.Call("pixelStorei", unpackAlignment, 4) 251 c.bindTexture(textureNative(t)) 252 253 gl.Call("texParameteri", texture2d, textureMagFilter, nearest) 254 gl.Call("texParameteri", texture2d, textureMinFilter, nearest) 255 gl.Call("texParameteri", texture2d, textureWrapS, clampToEdge) 256 gl.Call("texParameteri", texture2d, textureWrapT, clampToEdge) 257 258 // Firefox warns the usage of textures without specifying pixels (#629) 259 // 260 // Error: WebGL warning: drawElements: This operation requires zeroing texture data. This is slow. 261 // 262 // In Ebiten, textures are filled with pixels laster by the filter that ignores destination, so it is fine 263 // to leave textures as uninitialized here. Rather, extra memory allocating for initialization should be 264 // avoided. 265 gl.Call("texImage2D", texture2d, 0, rgba, width, height, 0, rgba, unsignedByte, nil) 266 267 return textureNative(t), nil 268 } 269 270 func (c *context) bindFramebufferImpl(f framebufferNative) { 271 c.ensureGL() 272 gl := c.gl 273 gl.Call("bindFramebuffer", framebuffer_, js.Value(f)) 274 } 275 276 func (c *context) framebufferPixels(f *framebuffer, width, height int) ([]byte, error) { 277 c.ensureGL() 278 gl := c.gl 279 280 c.bindFramebuffer(f.native) 281 282 p := jsutil.TemporaryUint8Array(4 * width * height) 283 gl.Call("readPixels", 0, 0, width, height, rgba, unsignedByte, p) 284 285 return jsutil.Uint8ArrayToSlice(p), nil 286 } 287 288 func (c *context) activeTexture(idx int) { 289 c.ensureGL() 290 gl := c.gl 291 gl.Call("activeTexture", texture0+idx) 292 } 293 294 func (c *context) bindTextureImpl(t textureNative) { 295 c.ensureGL() 296 gl := c.gl 297 gl.Call("bindTexture", texture2d, js.Value(t)) 298 } 299 300 func (c *context) deleteTexture(t textureNative) { 301 c.ensureGL() 302 gl := c.gl 303 if !gl.Call("isTexture", js.Value(t)).Bool() { 304 return 305 } 306 if c.lastTexture.equal(t) { 307 c.lastTexture = textureNative(js.Null()) 308 } 309 gl.Call("deleteTexture", js.Value(t)) 310 } 311 312 func (c *context) isTexture(t textureNative) bool { 313 // isTexture should not be called to detect context-lost since this performance is not good (#1175). 314 panic("opengl: isTexture is not implemented") 315 } 316 317 func (c *context) newFramebuffer(t textureNative) (framebufferNative, error) { 318 c.ensureGL() 319 gl := c.gl 320 f := gl.Call("createFramebuffer") 321 c.bindFramebuffer(framebufferNative(f)) 322 323 gl.Call("framebufferTexture2D", framebuffer_, colorAttachment0, texture2d, js.Value(t), 0) 324 if s := gl.Call("checkFramebufferStatus", framebuffer_); s.Int() != framebufferComplete.Int() { 325 return framebufferNative(js.Null()), errors.New(fmt.Sprintf("opengl: creating framebuffer failed: %d", s.Int())) 326 } 327 328 return framebufferNative(f), nil 329 } 330 331 func (c *context) setViewportImpl(width, height int) { 332 c.ensureGL() 333 gl := c.gl 334 gl.Call("viewport", 0, 0, width, height) 335 } 336 337 func (c *context) deleteFramebuffer(f framebufferNative) { 338 c.ensureGL() 339 gl := c.gl 340 if !gl.Call("isFramebuffer", js.Value(f)).Bool() { 341 return 342 } 343 // If a framebuffer to be deleted is bound, a newly bound framebuffer 344 // will be a default framebuffer. 345 // https://www.khronos.org/opengles/sdk/docs/man/xhtml/glDeleteFramebuffers.xml 346 if c.lastFramebuffer.equal(f) { 347 c.lastFramebuffer = framebufferNative(js.Null()) 348 c.lastViewportWidth = 0 349 c.lastViewportHeight = 0 350 } 351 gl.Call("deleteFramebuffer", js.Value(f)) 352 } 353 354 func (c *context) newShader(shaderType shaderType, source string) (shader, error) { 355 c.ensureGL() 356 gl := c.gl 357 s := gl.Call("createShader", int(shaderType)) 358 if jsutil.Equal(s, js.Null()) { 359 return shader(js.Null()), fmt.Errorf("opengl: glCreateShader failed: shader type: %d", shaderType) 360 } 361 362 gl.Call("shaderSource", js.Value(s), source) 363 gl.Call("compileShader", js.Value(s)) 364 365 if !gl.Call("getShaderParameter", js.Value(s), compileStatus).Bool() { 366 log := gl.Call("getShaderInfoLog", js.Value(s)) 367 return shader(js.Null()), fmt.Errorf("opengl: shader compile failed: %s", log) 368 } 369 return shader(s), nil 370 } 371 372 func (c *context) deleteShader(s shader) { 373 c.ensureGL() 374 gl := c.gl 375 gl.Call("deleteShader", js.Value(s)) 376 } 377 378 func (c *context) newProgram(shaders []shader, attributes []string) (program, error) { 379 c.ensureGL() 380 gl := c.gl 381 v := gl.Call("createProgram") 382 if jsutil.Equal(v, js.Null()) { 383 return program{}, errors.New("opengl: glCreateProgram failed") 384 } 385 386 for _, shader := range shaders { 387 gl.Call("attachShader", v, js.Value(shader)) 388 } 389 390 for i, name := range attributes { 391 gl.Call("bindAttribLocation", v, i, name) 392 } 393 394 gl.Call("linkProgram", v) 395 if !gl.Call("getProgramParameter", v, linkStatus).Bool() { 396 info := gl.Call("getProgramInfoLog", v).String() 397 return program{}, fmt.Errorf("opengl: program error: %s", info) 398 } 399 400 id := c.lastProgramID 401 c.lastProgramID++ 402 return program{ 403 value: v, 404 id: id, 405 }, nil 406 } 407 408 func (c *context) useProgram(p program) { 409 c.ensureGL() 410 gl := c.gl 411 gl.Call("useProgram", p.value) 412 } 413 414 func (c *context) deleteProgram(p program) { 415 c.ensureGL() 416 gl := c.gl 417 if !gl.Call("isProgram", p.value).Bool() { 418 return 419 } 420 gl.Call("deleteProgram", p.value) 421 } 422 423 func (c *context) getUniformLocationImpl(p program, location string) uniformLocation { 424 c.ensureGL() 425 gl := c.gl 426 return uniformLocation(gl.Call("getUniformLocation", p.value, location)) 427 } 428 429 func (c *context) uniformInt(p program, location string, v int) bool { 430 c.ensureGL() 431 gl := c.gl 432 l := c.locationCache.GetUniformLocation(c, p, location) 433 if l.equal(invalidUniform) { 434 return false 435 } 436 gl.Call("uniform1i", js.Value(l), v) 437 return true 438 } 439 440 func (c *context) uniformFloat(p program, location string, v float32) bool { 441 c.ensureGL() 442 gl := c.gl 443 l := c.locationCache.GetUniformLocation(c, p, location) 444 if l.equal(invalidUniform) { 445 return false 446 } 447 gl.Call("uniform1f", js.Value(l), v) 448 return true 449 } 450 451 func (c *context) uniformFloats(p program, location string, v []float32, typ shaderir.Type) bool { 452 c.ensureGL() 453 gl := c.gl 454 l := c.locationCache.GetUniformLocation(c, p, location) 455 if l.equal(invalidUniform) { 456 return false 457 } 458 459 base := typ.Main 460 if base == shaderir.Array { 461 base = typ.Sub[0].Main 462 } 463 464 arr8 := jsutil.TemporaryUint8Array(len(v) * 4) 465 arr := js.Global().Get("Float32Array").New(arr8.Get("buffer"), arr8.Get("byteOffset"), len(v)) 466 jsutil.CopySliceToJS(arr, v) 467 468 switch base { 469 case shaderir.Float: 470 gl.Call("uniform1fv", js.Value(l), arr) 471 case shaderir.Vec2: 472 gl.Call("uniform2fv", js.Value(l), arr) 473 case shaderir.Vec3: 474 gl.Call("uniform3fv", js.Value(l), arr) 475 case shaderir.Vec4: 476 gl.Call("uniform4fv", js.Value(l), arr) 477 case shaderir.Mat2: 478 gl.Call("uniformMatrix2fv", js.Value(l), false, arr) 479 case shaderir.Mat3: 480 gl.Call("uniformMatrix3fv", js.Value(l), false, arr) 481 case shaderir.Mat4: 482 gl.Call("uniformMatrix4fv", js.Value(l), false, arr) 483 default: 484 panic(fmt.Sprintf("opengl: unexpected type: %s", typ.String())) 485 } 486 487 return true 488 } 489 490 func (c *context) vertexAttribPointer(p program, index int, size int, dataType dataType, stride int, offset int) { 491 c.ensureGL() 492 gl := c.gl 493 gl.Call("vertexAttribPointer", index, size, int(dataType), false, stride, offset) 494 } 495 496 func (c *context) enableVertexAttribArray(p program, index int) { 497 c.ensureGL() 498 gl := c.gl 499 gl.Call("enableVertexAttribArray", index) 500 } 501 502 func (c *context) disableVertexAttribArray(p program, index int) { 503 c.ensureGL() 504 gl := c.gl 505 gl.Call("disableVertexAttribArray", index) 506 } 507 508 func (c *context) newArrayBuffer(size int) buffer { 509 c.ensureGL() 510 gl := c.gl 511 b := gl.Call("createBuffer") 512 gl.Call("bindBuffer", int(arrayBuffer), js.Value(b)) 513 gl.Call("bufferData", int(arrayBuffer), size, int(dynamicDraw)) 514 return buffer(b) 515 } 516 517 func (c *context) newElementArrayBuffer(size int) buffer { 518 c.ensureGL() 519 gl := c.gl 520 b := gl.Call("createBuffer") 521 gl.Call("bindBuffer", int(elementArrayBuffer), js.Value(b)) 522 gl.Call("bufferData", int(elementArrayBuffer), size, int(dynamicDraw)) 523 return buffer(b) 524 } 525 526 func (c *context) bindBuffer(bufferType bufferType, b buffer) { 527 c.ensureGL() 528 gl := c.gl 529 gl.Call("bindBuffer", int(bufferType), js.Value(b)) 530 } 531 532 func (c *context) arrayBufferSubData(data []float32) { 533 c.ensureGL() 534 gl := c.gl 535 arr8 := jsutil.TemporaryUint8Array(len(data) * 4) 536 arr := js.Global().Get("Float32Array").New(arr8.Get("buffer"), arr8.Get("byteOffset"), len(data)) 537 jsutil.CopySliceToJS(arr, data) 538 gl.Call("bufferSubData", int(arrayBuffer), 0, arr) 539 } 540 541 func (c *context) elementArrayBufferSubData(data []uint16) { 542 c.ensureGL() 543 gl := c.gl 544 arr8 := jsutil.TemporaryUint8Array(len(data) * 2) 545 arr := js.Global().Get("Uint16Array").New(arr8.Get("buffer"), arr8.Get("byteOffset"), len(data)) 546 jsutil.CopySliceToJS(arr, data) 547 gl.Call("bufferSubData", int(elementArrayBuffer), 0, arr) 548 } 549 550 func (c *context) deleteBuffer(b buffer) { 551 c.ensureGL() 552 gl := c.gl 553 gl.Call("deleteBuffer", js.Value(b)) 554 } 555 556 func (c *context) drawElements(len int, offsetInBytes int) { 557 c.ensureGL() 558 gl := c.gl 559 gl.Call("drawElements", triangles, len, unsignedShort, offsetInBytes) 560 } 561 562 func (c *context) maxTextureSizeImpl() int { 563 c.ensureGL() 564 gl := c.gl 565 return gl.Call("getParameter", maxTextureSize).Int() 566 } 567 568 func (c *context) getShaderPrecisionFormatPrecision() int { 569 c.ensureGL() 570 gl := c.gl 571 return gl.Call("getShaderPrecisionFormat", js.ValueOf(int(fragmentShader)), highFloat).Get("precision").Int() 572 } 573 574 func (c *context) flush() { 575 c.ensureGL() 576 gl := c.gl 577 gl.Call("flush") 578 } 579 580 func (c *context) needsRestoring() bool { 581 return !web.IsMobileBrowser() 582 } 583 584 func (c *context) canUsePBO() bool { 585 return isWebGL2Available 586 } 587 588 func (c *context) texSubImage2D(t textureNative, width, height int, args []*driver.ReplacePixelsArgs) { 589 c.ensureGL() 590 c.bindTexture(t) 591 gl := c.gl 592 // void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 593 // GLsizei width, GLsizei height, 594 // GLenum format, GLenum type, ArrayBufferView? pixels); 595 for _, a := range args { 596 arr := jsutil.TemporaryUint8Array(len(a.Pixels)) 597 jsutil.CopySliceToJS(arr, a.Pixels) 598 gl.Call("texSubImage2D", texture2d, 0, a.X, a.Y, a.Width, a.Height, rgba, unsignedByte, arr) 599 } 600 } 601 602 func (c *context) newPixelBufferObject(width, height int) buffer { 603 c.ensureGL() 604 gl := c.gl 605 b := gl.Call("createBuffer") 606 gl.Call("bindBuffer", int(pixelUnpackBuffer), js.Value(b)) 607 gl.Call("bufferData", int(pixelUnpackBuffer), 4*width*height, int(streamDraw)) 608 gl.Call("bindBuffer", int(pixelUnpackBuffer), nil) 609 return buffer(b) 610 } 611 612 func (c *context) replacePixelsWithPBO(buffer buffer, t textureNative, width, height int, args []*driver.ReplacePixelsArgs) { 613 c.ensureGL() 614 c.bindTexture(t) 615 gl := c.gl 616 gl.Call("bindBuffer", int(pixelUnpackBuffer), js.Value(buffer)) 617 618 stride := 4 * width 619 for _, a := range args { 620 arr := jsutil.TemporaryUint8Array(len(a.Pixels)) 621 jsutil.CopySliceToJS(arr, a.Pixels) 622 offset := 4 * (a.Y*width + a.X) 623 for j := 0; j < a.Height; j++ { 624 gl.Call("bufferSubData", int(pixelUnpackBuffer), offset+stride*j, arr, 4*a.Width*j, 4*a.Width) 625 } 626 } 627 628 // void texSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, 629 // GLsizei width, GLsizei height, 630 // GLenum format, GLenum type, GLintptr offset); 631 gl.Call("texSubImage2D", texture2d, 0, 0, 0, width, height, rgba, unsignedByte, 0) 632 gl.Call("bindBuffer", int(pixelUnpackBuffer), nil) 633 }