2019年8月26日 星期一

使圖片的 Blob 初始化 HTMLImageElement

使圖片的 Blob 初始化 HTMLImageElement

前言

  最近需要透過 Electron 來讀圖檔後轉成 WebGL 的 Texture ,但這個過程會有問題,Electron 讀檔後的資料型態是 UInt8Array ,本來想得很簡單,透過在如何直接把檔案的 Binary data 給 HTMLImage​Element裡所用的方法來餵資料,不幸的有問題,這裡把解決的過程做個紀錄。

內容

  從 Electron 讀檔後的資料型態為  UInt8Array ,本想編碼成 Base64 後再加上 MIME 的資訊後就能直接初始化 HTMLImageElement ,這個過程的問題是如何從 UInt8Array  裡判斷出 MIME 的資訊,難道要 Parse 裡面的資料嗎? 瀏覽器支援的圖片格式不少,這樣 Parse 的成本實在太高,所以找了一下網路的資料,在HTML – DOWNLOAD IMAGE THROUGH AJAX AND DISPLAY IT 裡發現它透過 Ajax 下載圖檔後,遽然不用 pasre 任何資料就可以初始化 HTMLImageElement ,是如何辦到的呢?它將下載完的 Blob 透過 window.URL.createObjectURL() 回傳的結果就可以直接初始化 HTMLImageElement ,範例如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <input id="btn" type="button" value="go" />
  <img id="img"></img>
</body>
</html>

Javascript的部分
window.onload=function(){
  document.getElementById("btn").onclick=function(evt){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
      if (xhr.readyState==4 && xhr.status==200) {
        var imgURL = window.URL.createObjectURL(xhr.response);
        var img = document.getElementById("img");
        img.onload = function(){
          window.URL.revokeObjectURL(xhr.response);
        }
        img.src = imgURL;
        //
        
      }
    }
    xhr.responseType = "blob";
    xhr.open("GET","https://i.imgur.com/rPv3FWk.jpg",true);
  
    xhr.send();
  }
}

在 Ajax 下載完後透過 window.URL.createObjectURL() 得到 imgURL ,接著直接拿  imgURL 來初始化 HTMLImageElement ,整個過程相當簡單。 imgURL 的內容是什麼?如果把他列應出來會發現是一個字串!一個類似網址的字串,可以想像是製造一個瀏覽器本地的檔案位置,當 HTMLImageElement  初始化完成後,透過 window.URL.revokeObjectURL() 來取消檔案位址。

  開頭說過 Electron 讀檔的資料型態為 UInt8Array,但範例需要的型態是 Blob ,如何轉換呢?可以參考  ArrayBuffer to blob conversion ,過程不會太複雜,範例如下
var blobData=new Blob( [uint8ArrayData] );

要注意的是要用 Array的型態來給,不能省略。

參考資料

[ MDN ] URL.createObjectURL()
ArrayBuffer to blob conversion
HTML – DOWNLOAD IMAGE THROUGH AJAX AND DISPLAY IT

相關文章

如何直接把檔案的 Binary data 給 HTMLImage​Element

2019年8月19日 星期一

建置 glslang 在 Windows

建置 glslang 在 Windows

前言

  最近研究 Vulkan 時發現 API 本身不提供 Shader 編譯器,官方的建議是利用官方提供的 glslangValidator ,這個程式可以在 Vulkan SDK 裡找到,利用 Console 命令編譯 Shader後輸出Spir-V程式碼。但如果需要在 Runtime 時編譯 Shader 的話就需要自己編譯一個編譯器,幸運的是官方有提供編譯器,但不幸的是官方不會 Build 好在 Vulkan SDK ,只能自己 Build,由於 Build 的過程有些繁雜,在此將過程做個紀錄。

內容

  Vulkan 官方提供兩個編譯器,glslang 與 shaderc,這兩個編譯器的專案都在 Vulkan SDK 裡面,但為什麼官方需要兩個編譯器呢?在官方的範例 (Vulkan SDK)\Samples\API-Samples\utils\util.cpp 裡有一段程式碼如下
bool GLSLtoSPV(const VkShaderStageFlagBits shader_type, const char *pshader, std::vector<unsigned int> &spirv) {
#ifndef __ANDROID__
    EShLanguage stage = FindLanguage(shader_type);
    glslang::TShader shader(stage);
    glslang::TProgram program;
    const char *shaderStrings[1];
    TBuiltInResource Resources = {};
    init_resources(Resources);

    // Enable SPIR-V and Vulkan rules when parsing GLSL
    EShMessages messages = (EShMessages)(EShMsgSpvRules | EShMsgVulkanRules);

    shaderStrings[0] = pshader;
    shader.setStrings(shaderStrings, 1);

    if (!shader.parse(&Resources, 100, false, messages)) {
        puts(shader.getInfoLog());
        puts(shader.getInfoDebugLog());
        return false;  // something didn't work
    }

    program.addShader(&shader);

    //
    // Program-level processing...
    //

    if (!program.link(messages)) {
        puts(shader.getInfoLog());
        puts(shader.getInfoDebugLog());
        fflush(stdout);
        return false;
    }

    glslang::GlslangToSpv(*program.getIntermediate(stage), spirv);
#else
    // On Android, use shaderc instead.
    shaderc::Compiler compiler;
    shaderc::SpvCompilationResult module =
        compiler.CompileGlslToSpv(pshader, strlen(pshader), MapShadercType(shader_type), "shader");
    if (module.GetCompilationStatus() != shaderc_compilation_status_success) {
        LOGE("Error: Id=%d, Msg=%s", module.GetCompilationStatus(), module.GetErrorMessage().c_str());
        return false;
    }
    spirv.assign(module.cbegin(), module.cend());
#endif
    return true;
}

 GLSLtoSPV 是把 GLSL 編成 Spir-V 碼, Function 在開始就使用 Preprocessor 隔開,一邊是"非Android",另一邊是"Android"。在"非Android"時會使用 glslang ,另一邊則是 shaderc, glslang 完全沒出現在 (Vulkan SDK)\include 裡,所以要自己 Build ,這沒什麼問題,但 shaderc 的部分遽然有出現在 (Vulkan SDK)\include !可惜我實際 include 後會有錯誤,所以還是要建置  glslang 。

  glslang 所使用的建置是 CMake ,設定的方式可以參考下圖
使用CMake 建置 glslang
在"1"的位置設定 glslang 的專案位置,"2"的部分是建置的目標位置,這裡是直接在 glslang 的資料夾下產生 ProjWindows ,並把目標為位置設定在那,接著按下"3",就可以取得組態,組態取完後按下"4"就可以產生 Visual studio 方案。開啟方案後請建置"SPIRV"專案,如下圖
建置"SPIRV"專案

接著手動建置 Release 組態的"spirv-remap"專案,如下圖
手動建置 Release 組態的"spirv-remap"專案

這個步驟的主要目的產生 Release 的 SPVRemapper.lib,建置 Release 的"SPIRV"專案並不會產生,所以請手動建置。SPVRemapper.lib雖不需要用在編譯,但如果需要建置 Vulkan SDK 裡的其它專案時會用到,請不要省略此步驟。不幸的是建置完後並不會將所有相關的 lib 都集中起來,所以要自己 Copy ,這裡整理了需要 Copy 的檔案,清單如下
(CMake build target)\glslang\Debug\glslangd.lib
(CMake build target)\glslang\Release\glslang.lib
(CMake build target)\hlsl\Debug\HLSLd.lib
(CMake build target)\hlsl\Release\HLSL.lib
(CMake build target)\OGLCompilersDLL\Debug\OGLCompilerd.lib
(CMake build target)\OGLCompilersDLL\Release\OGLCompiler.lib
(CMake build target)\glslang\OSDependent\Windows\Debug\OSDependentd.lib
(CMake build target)\glslang\OSDependent\Windows\Release\OSDependent.lib
(CMake build target)\SPIRV\Debug\SPIRVd.lib
(CMake build target)\SPIRV\Release\SPIRV.lib
(CMake build target)\External\spirv-tools\source\Debug\SPIRV-Toolsd.lib
(CMake build target)\External\spirv-tools\source\Release\SPIRV-Tools.lib
(CMake build target)\External\spirv-tools\source\opt\Debug\SPIRV-Tools-optd.lib
(CMake build target)\External\spirv-tools\source\opt\Release\SPIRV-Tools-opt.lib

  最後還有需要 include 的檔案,這個比較簡單, (Vulkan SDK)\glslang\glslang 與  (Vulkan SDK)\glslang\SPIRV 兩個資料夾 的內容就是所有需要 include 的檔案,請示需要 Copy。

參考資料

Vulkan Tutorial

2019年8月12日 星期一

整理 WebGL2 與 WebGL1 的差異

整理 WebGL2 與 WebGL1 的差異

前言

  WebGL2 已經推出有一段時間了,自己學 WebGL2 也有一段時間了,這裡就把 WebGL2 與 WebGL1 的差異做個比較,順便做個紀錄。

內容

  在 WebGL1 的時候取得 Context 的程式碼如下
var context =  canvas.getContext( 'webgl') ||
               canvas.getContext( 'webkit-3d') ||
               canvas.getContext( 'experimental-webgl')||
               canvas.getContext( 'moz-3d');

WebGL2在取得 Context 的程式碼如下
var context =  canvas.getContext( 'webgl2');

WebGL2 在這部分比較簡單,因為沒有歷史的包袱。WebGL2 相容於 WebGL1,所以舊的程式應該能沿用,要注意的是依據 Extension 來執行的程式碼,有些 Extension 在WebGL1會出現,但在 WebGL2 卻不會再顯示,如 Ext_sRGB 與 WEBGL_depth_texture。

  根據  [ WiKi ] WebGL 裡所說WebGL2是基於 OpenGLES3 ,但在 Shader model 方面差相當的多, OpenGLES3 的 Shader mpdel 如下
OpenGLES3 的 Shader model
而 WebGL1 與 WebGL2 的 Shader model 如下
WebGL 的 Shader model

這裡可以看到 WebGL2 在 Shader model 是沒有改變的,所以不支援 Geometry shader 與 Tessellation shader。

  WebGL2 開始支援 UBO (Uniform buffer object)與 Sampler Object,用法跟 OpenGL 的用法一樣沒改,但 Sampler Object 使用要注意之前我發現的 Electron搭配WebGL使用的Bug。WebGL2 支援 Transform feedback ,但這個功能我沒用過以後再研究。

  最後介紹一下一些實用的網站,在 查詢瀏覽器支援 WebGL2 的狀況 可以查詢各個瀏覽器對於 WebGL2 的支援狀況,如果要查詢目前瀏覽器的支援狀況可以到 WebGL Report,網址是 查詢目前瀏覽器是否支援 WebGL2,開啟後可以看到下圖
查詢目前瀏覽器對於 WebGL 的支援狀況
不只可以查 WebGL2 ,WebGL1也可以順便查, Extension 的部分也會整個列印出來,用起來相當方便。

參考資料

查詢瀏覽器支援 WebGL2 的狀況
查詢目前瀏覽器是否支援 WebGL2
[ WiKi ] WebGL
[ MDN ] WebGL2RenderingContext

2019年8月5日 星期一

解決多個 WebGLRenderContext 之間無法共享資源的問題

解決多個 WebGLRenderContext 之間無法共享資源的問題

前言

  在 多個 WebGLRenderContext 的問題 說過 WebGLRenderContext 之間的資源無法共享的問題,這樣像遊戲編輯器需要同時要繪製遊戲結果也要繪製資源(如Texture)的話該如何解決,目前想到這個方案"可能"可以解決,在此做個紀錄。

內容

  解決的方案是利用 WebGLRenderContext  先繪製資源後再輸出成 ImageElement ,接著在繪製遊戲結果,由於都是使用同一個 WebGLRenderContext   來繪製,所以不會有資源共享的問題,但有個問題,問題會發生在繪製資源時 CanvasElement 的大小與繪製遊戲結果時CanvasElement  的大小如果"不一樣",如果不一樣是否會在畫面上看到 CanvasElement  大小不斷變化的結果?目前在 Chrome 與 Firefox 實驗的結果是不會看到 CanvasElement 忽大忽小的過程,這樣雖然解決問題,但我目前還找不到也類似的範例與教學這樣使用 CanvasElement  ,也就是說這個行為可能不是被保證可以用的。

  雖然這個方案不保證可以用,但它是目前唯一可以用的方案,留下這個方案的範例方便以後實驗與研究。具體的範例如下
HTML 的部分
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
<canvas id="myCanvas" width="400px" height="300px"></canvas>
<hr>
<img id='myImage'></img>
</body>
</html>

JavaScript 的部分
let canvas = document.getElementById('myCanvas');
let glCTX = canvas.getContext('webgl');
let vertShader = glCTX.createShader(glCTX.VERTEX_SHADER);
glCTX.shaderSource(
  vertShader , 
  'attribute vec3 pos;void main(void){gl_Position=vec4(pos, 1.0);}'
);
glCTX.compileShader(vertShader);
let fragShader = glCTX.createShader(glCTX.FRAGMENT_SHADER);
glCTX.shaderSource(
  fragShader, 
  'precision mediump float;uniform vec4 myColor;void main(void){gl_FragColor=myColor;}'
);
glCTX.compileShader(fragShader);
let prog = glCTX.createProgram();
glCTX.attachShader(prog, vertShader);
glCTX.attachShader(prog, fragShader);
glCTX.linkProgram(prog);
//
let vertexBuf1 = glCTX.createBuffer();
glCTX.bindBuffer(glCTX.ARRAY_BUFFER, vertexBuf1);
glCTX.bufferData(
  glCTX.ARRAY_BUFFER, 
  new Float32Array([ 0.0,-0.9,0.0,  -0.9,0.9,0.0,  0.9,0.9,0.0 ]), 
  glCTX.STATIC_DRAW
);
let vertexBuf2 = glCTX.createBuffer();
glCTX.bindBuffer(glCTX.ARRAY_BUFFER, vertexBuf2);
glCTX.bufferData(
  glCTX.ARRAY_BUFFER, 
  new Float32Array([ 0.0,0.5,0.0,  -0.5,-0.5,0.0,  0.5,-0.5,0.0 ]), 
  glCTX.STATIC_DRAW
);
let timeRad=0.0;
let colorValue=[0.0, 0.0, 0.0, 1.0];
function simpleDraw(glContext,vbo,colorArray){
  glContext.useProgram(prog);
  //
  glContext.viewport(0,0,glContext.canvas.width,glContext.canvas.height);
  glContext.clearColor(0, 0, 1, 1);
  glContext.clear(glContext.COLOR_BUFFER_BIT);
  glContext.bindBuffer(glContext.ARRAY_BUFFER, vbo);
  //
  let posLoc = glContext.getAttribLocation(prog, "pos");
  glContext.vertexAttribPointer(posLoc, 3, glContext.FLOAT, false, 0, 0);
  glContext.enableVertexAttribArray(posLoc);

  let colorLoc = glContext.getUniformLocation(prog, 'myColor');
  glContext.uniform4f(colorLoc,colorArray[0],colorArray[1],colorArray[2], colorArray[3] );
  
  glContext.drawArrays(glContext.TRIANGLES, 0, 3);
}
function myRender(){
  timeRad+=0.05;
  colorValue[0]=Math.sin(timeRad);
  glCTX.canvas.width = 400;
  glCTX.canvas.height = 300;
  simpleDraw(glCTX,vertexBuf1,colorValue);
  let imgEle = document.getElementById('myImage');
  imgEle.src=glCTX.canvas.toDataURL();
  
  //
  glCTX.canvas.width=200;
  glCTX.canvas.height=150;
  simpleDraw(glCTX,vertexBuf2,[1.0,1.0,1.0,1.0]);
  //
  window.requestAnimationFrame(myRender);
}
window.onload = function(){
  window.requestAnimationFrame(myRender);
  
}

執行結果如下
執行結果

這次的範例由 多個 WebGLRenderContext 的問題 更改而來, simpleDraw()會依據輸入 VBO 與 顏色繪製圖形,在 myRender() 會先將 CanvasElement  放大成 400*300,接著繪製 vertexBuf1與輸入的顏色, vertexBuf1 是一個倒三角,顏色的部分會依據時間從黑變紅,再由紅變黑持續轉變,接著將 CanvasElement  結果輸出圖片到 ImageElement ,也就是執行結果圖片下方的部分,如果對輸出圖片有疑問請參考 將canvas的結果輸出到image ,緊接著把 CanvasElement  放大成 200*150 ,繪製 vertexBuf2 ,顏色是白色。這次特地使用 requestAnimationFrame() 來更新畫面,主要是為了實驗在畫面持續更新下會不會發生 CanvasElement  忽大忽小的問題。

參考資料

Drawing a triangle with WebGL

相關文章

多個 WebGLRenderContext 的問題
將canvas的結果輸出到image

2019年8月2日 星期五

Intel HD Graphics 630 初始化 Direct3D12 會顯示不明例外

Intel HD Graphics 630 初始化 Direct3D12 會顯示不明例外

前言

  最近要將 Direct3D12 導入,但發現驅動程式有 bug ,在此做個紀錄,方便日後追蹤。

內容

  狀況發生在初始化 Direct3D12 的時候會拋擲不明例外,如下圖
初始化 Direct3D12 拋擲不明例外

圖中使用 Direct3D12 官方的 D3D12HelloWorld 方案裡的 D3D12HelloTriangle 專案,每當 D3D12CreateDevice() 初始化完畢後會顯示不明例外,這例外只是訊息,程式不會 throw exception ,雖然不會影響執行結果,但正常不應該顯示任何例外。