zoukankan      html  css  js  c++  java
  • 从0开发3D引擎(十二):使用领域驱动设计,从最小3D程序中提炼引擎(第三部分)

    大家好,本文根据领域驱动设计的成果,实现了init API。

    上一篇博文

    从0开发3D引擎(十一):使用领域驱动设计,从最小3D程序中提炼引擎(第二部分)

    下一篇博文

    从0开发3D引擎(十三):使用领域驱动设计,从最小3D程序中提炼引擎(第四部分)

    继续实现

    实现“DirectorJsAPI.init”

    实现“保存WebGL上下文”限界上下文

    1、在src/api_layer/api/中加入DirectorJsAPI.re,实现API
    DirectorJsAPI.re代码为:

    let init = DirectorApService.init;
    

    2、在src/infrastructure_layer/external/external_object/中加入Error.re,负责处理“js异常”这个外部对象
    Error.re代码为:

    //根据错误信息(string类型),创建并抛出“js异常”对象
    let error = msg => Js.Exn.raiseError(msg);
    
    //根据“js异常”对象,抛出它
    let throwError: Js.Exn.t => unit = [%raw err => {|
    throw err;
    |}];
    

    3、在src/application_layer/service/中加入DirectorApService.re,实现应用服务
    DirectorApService.re代码为:

    let init = contextConfigJsObj => {
      CanvasCanvasEntity.getCanvas()
      |> OptionContainerDoService.get
      //OptionContainerDoService.get函数返回的是Result容器的包装值,需要调用ResultContainerVO.bind函数来处理容器内部的值
      |> ResultContainerVO.bind(canvas => {
           SetWebGLContextSetWebGLContextDoService.setGl(
             contextConfigJsObj,
             canvas,
           )
         })
         //应用服务DirectorApService负责用抛出异常的方式处理Result错误
         |> ResultContainerVO.handleFail(Error.throwError);
    };
    

    关于bind函数的使用,可以参考从0开发3D引擎(五):函数式编程及其在引擎中的应用->bind

    4、修改CanvasCanvasEntity.re,实现getCanvas函数
    CanvasCanvasEntity.re相关代码为:

    let getCanvas = () => {
      Repo.getCanvas();
    };
    

    5、把最小3D程序的WebGL1.re放到src/infrastructure_layer/external/library/中,保留所有的代码
    WebGL1.re代码为:

    open Js.Typed_array;
    
    type webgl1Context;
    
    type program;
    
    type shader;
    
    type buffer;
    
    type attributeLocation = int;
    
    type uniformLocation;
    
    type bufferTarget =
      | ArrayBuffer
      | ElementArrayBuffer;
    
    type usage =
      | Static;
    
    type contextConfigJsObj = {
      .
      "alpha": bool,
      "depth": bool,
      "stencil": bool,
      "antialias": bool,
      "premultipliedAlpha": bool,
      "preserveDrawingBuffer": bool,
    };
    
    [@bs.send]
    external getWebGL1Context:
      ('canvas, [@bs.as "webgl"] _, contextConfigJsObj) => webgl1Context =
      "getContext";
    
    [@bs.send.pipe: webgl1Context] external createProgram: program = "";
    
    [@bs.send.pipe: webgl1Context] external useProgram: program => unit = "";
    
    [@bs.send.pipe: webgl1Context] external linkProgram: program => unit = "";
    
    [@bs.send.pipe: webgl1Context]
    external shaderSource: (shader, string) => unit = "";
    
    [@bs.send.pipe: webgl1Context] external compileShader: shader => unit = "";
    
    [@bs.send.pipe: webgl1Context] external createShader: int => shader = "";
    
    [@bs.get] external getVertexShader: webgl1Context => int = "VERTEX_SHADER";
    
    [@bs.get] external getFragmentShader: webgl1Context => int = "FRAGMENT_SHADER";
    
    [@bs.get] external getHighFloat: webgl1Context => int = "HIGH_FLOAT";
    
    [@bs.get] external getMediumFloat: webgl1Context => int = "MEDIUM_FLOAT";
    
    [@bs.send.pipe: webgl1Context]
    external getShaderParameter: (shader, int) => bool = "";
    
    [@bs.get] external getCompileStatus: webgl1Context => int = "COMPILE_STATUS";
    
    [@bs.get] external getLinkStatus: webgl1Context => int = "LINK_STATUS";
    
    [@bs.send.pipe: webgl1Context]
    external getProgramParameter: (program, int) => bool = "";
    
    [@bs.send.pipe: webgl1Context]
    external getShaderInfoLog: shader => string = "";
    
    [@bs.send.pipe: webgl1Context]
    external getProgramInfoLog: program => string = "";
    
    [@bs.send.pipe: webgl1Context]
    external attachShader: (program, shader) => unit = "";
    
    [@bs.send.pipe: webgl1Context]
    external bindAttribLocation: (program, int, string) => unit = "";
    
    [@bs.send.pipe: webgl1Context] external deleteShader: shader => unit = "";
    
    [@bs.send.pipe: webgl1Context] external createBuffer: buffer = "";
    
    [@bs.get]
    external getArrayBuffer: webgl1Context => bufferTarget = "ARRAY_BUFFER";
    
    [@bs.get]
    external getElementArrayBuffer: webgl1Context => bufferTarget =
      "ELEMENT_ARRAY_BUFFER";
    
    [@bs.send.pipe: webgl1Context]
    external bindBuffer: (bufferTarget, buffer) => unit = "";
    
    [@bs.send.pipe: webgl1Context]
    external bufferFloat32Data: (bufferTarget, Float32Array.t, usage) => unit =
      "bufferData";
    
    [@bs.send.pipe: webgl1Context]
    external bufferUint16Data: (bufferTarget, Uint16Array.t, usage) => unit =
      "bufferData";
    
    [@bs.get] external getStaticDraw: webgl1Context => usage = "STATIC_DRAW";
    
    [@bs.send.pipe: webgl1Context]
    external getAttribLocation: (program, string) => attributeLocation = "";
    
    [@bs.send.pipe: webgl1Context]
    external getUniformLocation: (program, string) => Js.Null.t(uniformLocation) =
      "";
    
    [@bs.send.pipe: webgl1Context]
    external vertexAttribPointer:
      (attributeLocation, int, int, bool, int, int) => unit =
      "";
    
    [@bs.send.pipe: webgl1Context]
    external enableVertexAttribArray: attributeLocation => unit = "";
    
    "";
    
    [@bs.send.pipe: webgl1Context]
    external uniformMatrix4fv: (uniformLocation, bool, Float32Array.t) => unit =
      "";
    
    [@bs.send.pipe: webgl1Context]
    external uniform1i: (uniformLocation, int) => unit = "";
    
    [@bs.send.pipe: webgl1Context]
    external uniform3f: (uniformLocation, float, float, float) => unit = "";
    
    [@bs.send.pipe: webgl1Context]
    external drawElements: (int, int, int, int) => unit = "";
    
    [@bs.get] external getFloat: webgl1Context => int = "FLOAT";
    
    [@bs.send.pipe: webgl1Context]
    external clearColor: (float, float, float, float) => unit = "";
    
    [@bs.send.pipe: webgl1Context] external clear: int => unit = "";
    
    [@bs.get]
    external getColorBufferBit: webgl1Context => int = "COLOR_BUFFER_BIT";
    
    [@bs.get]
    external getDepthBufferBit: webgl1Context => int = "DEPTH_BUFFER_BIT";
    
    [@bs.get] external getDepthTest: webgl1Context => int = "DEPTH_TEST";
    
    [@bs.send.pipe: webgl1Context] external enable: int => unit = "";
    
    [@bs.get] external getTriangles: webgl1Context => int = "TRIANGLES";
    
    [@bs.get] external getUnsignedShort: webgl1Context => int = "UNSIGNED_SHORT";
    
    [@bs.get] external getCullFace: webgl1Context => int = "CULL_FACE";
    
    [@bs.send.pipe: webgl1Context] external cullFace: int => unit = "";
    
    [@bs.get] external getBack: webgl1Context => int = "BACK";
    

    6、在src/domain_layer/domain/init/set_webgl_context/service/中加入SetWebGLContextSetWebGLContextDoService.re,创建领域服务SetWebGLContext
    SetWebGLContextSetWebGLContextDoService.re代码为:

    let setGl = (contextConfigJsObj, canvas): ResultContainerVO.t(unit, Js.Exn.t) => {
      ContextContextEntity.setGl(contextConfigJsObj, canvas)
      |> ResultContainerVO.succeed;
    };
    

    7、修改ContextContextEntity.re,实现setGl函数
    ContextContextEntity.re相关代码为:

    let setGl = (contextConfigJsObj, canvas) => {
      ContextRepo.setGl(WebGL1.getWebGL1Context(canvas, contextConfigJsObj));
    };
    

    8、修改ContextPOType.re,定义Context PO的gl字段的数据类型
    ContextPOType.re相关代码为:

    type context = {
      gl: option(WebGL1.webgl1Context),
      ...
    };
    

    9、修改ContextRepo.re,实现仓库对Context PO的gl字段的操作
    ContextRepo.re代码为:

    let getGl = gl => {
      //将Option转换为Result
      Repo.getContext().gl |> OptionContainerDoService.get;
    };
    
    let setGl = gl => {
      Repo.setContext({...Repo.getContext(), gl: Some(gl)});
    };
    

    10、修改CreateRepo.re,实现创建Context PO的gl字段
    CreateRepo.re相关代码为:

    let create = () => {
      ...
      context: {
        gl: None,
        ...
      },
    };
    

    实现“初始化所有Shader”限界上下文

    1、重写DirectorApService.re
    DirectorApService.re代码为:

    let init = contextConfigJsObj => {
      CanvasCanvasEntity.getCanvas()
      |> ResultContainerVO.bind(canvas => {
           SetWebGLContextSetWebGLContextDoService.setGl(
             contextConfigJsObj,
             canvas,
           )
           |> ResultContainerVO.bind(() => {InitShaderInitShaderDoService.init()})
         })
      |> ResultContainerVO.handleFail(Error.throwError);
    };
    

    2、加入值对象InitShader

    从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)的“设计值对象InitShader”中,我们已经定义了值对象InitShader的类型,所以我们直接将设计转换为实现:
    在src/domain_layer/domain/init/init_shader/value_object/中加入InitShaderInitShaderVO.re,创建值对象InitShader
    InitShaderInitShaderVO.re代码为:

    type singleInitShader = {
      shaderId: string,
      vs: string,
      fs: string,
    };
    
    type t = list(singleInitShader);
    

    3、在src/domain_layer/domain/shader/shader/value_object/中加入ProgramShaderVO.re,创建值对象Program,它的DO对应一个WebGL的program对象
    ProgramShaderVO.re代码为:

    type t =
      | Program(WebGL1.program);
    
    let create = program => Program(program);
    
    let value = program =>
      switch (program) {
      | Program(value) => value
      };
    

    4、修改聚合根ShaderManager的DO

    根据识别的引擎逻辑:

    • 在初始化所有Shader时,创建每个Program
    • 在渲染每个三角形时,根据Shader名称获得关联的Program

    我们需要根据Shader id获得关联的Program,所以在ShaderManager DO中应该加入一个immutable hash map,它的key为Shader id,value为值对象Program的DO。

    应该在领域视图的“容器”限界上下文中,加入值对象ImmutableHashMap、值对象MutableHashMap,其中ImmutableHashMap用于实现不可变的hash map,MutableHashMap用于实现可变的hash map。

    现在来具体实现它们:
    1)在src/domain_layer/domain/structure/container/value_object/中创建文件夹hash_map/
    2)在hash_map/文件夹中加入ImmutableHashMapContainerVO.re、MutableHashMapContainerVO.re、HashMapContainer.re、HashMapContainerType.re
    ImmutableHashMapContainerVO.re负责实现Immutable Hash Map;
    MutableHashMapContainerVO.re负责实现Mutable Hash Map;
    HashMapContainer.re从两者中提出的公共代码;
    HashMapContainerType.re定义HashMap的类型。

    因为HashMapContainer需要使用reduce来遍历数组,这个操作属于通用操作,应该作为领域服务,所以在领域视图的“容器”限界上下文中,加入领域服务Array。在src/domain_layer/domain/structure/container/service/中加入ArrayContainerDoService.re,创建领域服务Array。

    相关代码如下:
    ArrayContainterDoService.re

    let reduceOneParam = (func, param, arr) => {
      //此处为了优化,使用for循环和mutable变量来代替Array.reduce
      let mutableParam = ref(param);
      for (i in 0 to Js.Array.length(arr) - 1) {
        mutableParam := func(. mutableParam^, Array.unsafe_get(arr, i));
      };
      mutableParam^;
    };
    

    HashMapContainerType.re

    type t('key, 'value) = Js.Dict.t('value);
    type t2('value) = t(string, 'value);
    

    HashMapContainer.re

    let createEmpty = (): HashMapContainerType.t2('a) => Js.Dict.empty();
    
    let get = (key: string, map: HashMapContainerType.t2('a)) =>
      Js.Dict.get(map, key);
    
    let entries = (map: HashMapContainerType.t2('a)): array((Js.Dict.key, 'a)) =>
      map |> Js.Dict.entries;
    
    let _mutableSet = (key: string, value, map) => {
      Js.Dict.set(map, key, value);
      map;
    };
    
    let _createEmpty = (): Js.Dict.t('a) => Js.Dict.empty();
    
    let copy = (map: HashMapContainerType.t2('a)): HashMapContainerType.t2('a) =>
      map
      |> entries
      |> ArrayContainerDoService.reduceOneParam(
           (. newMap, (key, value)) => newMap |> _mutableSet(key, value),
           _createEmpty(),
         );
    

    ImmutableHashMapContainerVO.re

    type t('key, 'value) = HashMapContainerType.t('key, 'value);
    
    let createEmpty = HashMapContainer.createEmpty;
    
    let set =
        (key: string, value: 'a, map: HashMapContainerType.t2('a))
        : HashMapContainerType.t2('a) => {
      let newMap = map |> HashMapContainer.copy;
    
      Js.Dict.set(newMap, key, value);
    
      newMap;
    };
    
    let get = HashMapContainer.get;
    

    MutableHashMap.re

    type t('key, 'value) = HashMapContainerType.t('key, 'value);
    
    let createEmpty = HashMapContainer.createEmpty;
    
    let set = (key: string, value: 'a, map: HashMapContainerType.t2('a)) => {
      Js.Dict.set(map, key, value);
    
      map;
    };
    
    let get = HashMapContainer.get;
    

    现在我们可以通过修改ShaderManagerShaderEntity.re来修改ShaderManager的DO,加入programMap字段
    ShaderManagerShaderEntity.re相关代码为:

    type t = {
      ...
      programMap:
        ImmutableHashMapContainerVO.t2(ShaderShaderEntity.t, ProgramShaderVO.t),
    };
    

    5、创建领域服务BuildInitShaderData,实现构造值对象InitShader
    1)在src/domain_layer/domain/init/init_shader/service/中加入BuildInitShaderDataInitShaderDoService.re,创建领域服务BuildInitShaderData
    BuildInitShaderDataInitShaderDoService.re代码为:

    let build = () => {
      ShaderManagerShaderEntity.getAllGLSL()
      |> List.map(((shaderName, glsl)) => {
           (
             {
               shaderId: ShaderShaderEntity.getId(shaderName),
               vs: GLSLShaderVO.getVS(glsl),
               fs: GLSLShaderVO.getFS(glsl),
             }: InitShaderInitShaderVO.singleInitShader
           )
         });
    };
    

    2)修改GLSLShaderVO.re,实现getVS、getFS函数
    GLSLShaderVO.re相关代码为:

    let getVS = glsl =>
      switch (glsl) {
      | GLSL(vs, fs) => vs
      };
    
    let getFS = glsl =>
      switch (glsl) {
      | GLSL(vs, fs) => fs
      };
    

    3)修改ShaderManagerShaderEntity.re,加入getAllGLSL函数
    ShaderManagerShaderEntity.re相关代码为:

    let getAllGLSL = () => {
      ShaderManagerRepo.getAllGLSL();
    };
    

    4)修改ShaderManagerRepo.re,加入getAllGLSL函数
    ShaderManagerShaderEntity.re相关代码为:

    let getAllGLSL = () => {
      Repo.getShaderManager().glsls
      |> List.map(((shaderId, (vs, fs))) => {
           (ShaderShaderEntity.create(shaderId), GLSLShaderVO.create((vs, fs)))
         });
    };
    

    6、在src/domain_layer/domain/init/init_shader/service/中加入InitShaderInitShaderDoService.re,创建领域服务InitShader
    InitShaderInitShaderDoService.re代码为:

    let init = (): ResultContainerVO.t(unit, Js.Exn.t) => {
      ContextContextEntity.getGl()
      |> ResultContainerVO.bind(gl => {
           //从着色器DO数据中构建值对象InitShader
           BuildInitShaderDataInitShaderDoService.build()
           |> ResultContainerVO.tryCatch(initShaderData => {
                initShaderData
                |> List.iter(
                     (
                       {shaderId, vs, fs}: InitShaderInitShaderVO.singleInitShader,
                     ) => {
                     let program = ContextContextEntity.createProgram(gl);
    
                     /* 注意:领域服务不应该直接依赖Repo
    
                        应该通过实体ContextContextEntity而不是ShaderManagerRepo来将program设置到ShaderManager PO的programMap中!
                        */
                     ContextContextEntity.setProgram(shaderId, program);
    
                     ContextContextEntity.initShader(vs, fs, program, gl)
                     |> ignore;
    
                     //用于运行测试
                     Js.log((shaderId, vs, fs));
                   })
              })
         });
    };
    

    7、修改ContextContextEntity.re,实现相关函数
    ContextContextEntity.re相关代码为:

    let getGl = () => {
      ContextRepo.getGl();
    };
    
    ...
    
    let createProgram = gl => gl |> WebGL1.createProgram;
    
    let setProgram = (shaderId, program) => {
      ShaderManagerRepo.setProgram(shaderId, program);
    };
    
    let _compileShader = (gl, glslSource, shader) => {
      WebGL1.shaderSource(shader, glslSource, gl);
      WebGL1.compileShader(shader, gl);
    
      WebGL1.getShaderParameter(shader, WebGL1.getCompileStatus(gl), gl)
      === false
        ? {
          let message = WebGL1.getShaderInfoLog(shader, gl);
    
          //这里为了实现“从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)”提出的“处理错误优化”,用“抛出异常”而不是Result来处理错误
          Error.error(
            {j|shader info log: $message
            glsl source: $glslSource
            |j},
          );
        }
        : shader;
    };
    
    let _linkProgram = (program, gl) => {
      WebGL1.linkProgram(program, gl);
    
      WebGL1.getProgramParameter(program, WebGL1.getLinkStatus(gl), gl) === false
        ? {
          let message = WebGL1.getProgramInfoLog(program, gl);
    
          //这里为了实现“从0开发3D引擎(十):使用领域驱动设计,从最小3D程序中提炼引擎(第一部分)”提出的“处理错误优化”,用“抛出异常”而不是Result来处理错误
          Error.error({j|link program error: $message|j});
        }
        : program;
    };
    
    let initShader = (vsSource: string, fsSource: string, program, gl) => {
      let vs =
        _compileShader(
          gl,
          vsSource,
          WebGL1.createShader(WebGL1.getVertexShader(gl), gl),
        );
      let fs =
        _compileShader(
          gl,
          fsSource,
          WebGL1.createShader(WebGL1.getFragmentShader(gl), gl),
        );
    
      WebGL1.attachShader(program, vs, gl);
      WebGL1.attachShader(program, fs, gl);
    
      WebGL1.bindAttribLocation(program, 0, "a_position", gl);
    
      _linkProgram(program, gl);
    
      WebGL1.deleteShader(vs, gl);
      WebGL1.deleteShader(fs, gl);
    
      program;
    };
    

    8、修改ShaderManagerPOType.re,ShaderManager PO加入programMap字段

    虽然programMap也是hash map,但不能直接使用领域层的值对象ImmutableHashMapContainerVO来定义它的类型!因为PO属于基础设施层,它不能依赖领域层!
    因此,我们应该在基础设施层的“数据”中创建一个ImmutableHashMap.re模块,尽管它的类型和函数都与ImmutableHashMapContainerVO一样。

    在src/infrastructure_layer/data/中创建文件夹structure/,在该文件夹中加入ImmutableHashMap.re。
    为了方便,目前暂时直接用ImmutableHashMapContainerVO来实现ImmutableHashMap。
    ImmutableHashMap.re代码为:

    type t2('key, 'a) = ImmutableHashMapContainerVO.t2('key, 'a);
    
    let createEmpty = ImmutableHashMapContainerVO.createEmpty;
    
    let set = ImmutableHashMapContainerVO.set;
    

    修改ShaderManagerPOType.re,ShaderManager PO加入programMap字段:

    type shaderManager = {
      ...
      programMap: ImmutableHashMap.t2(shaderId, WebGL1.program),
    };
    

    9、修改ShaderManagerRepo.re,实现setProgram函数
    ShaderManagerRepo.re相关代码为:

    let _getProgramMap = ({programMap}) => programMap;
    
    let setProgram = (shaderId, program) => {
      Repo.setShaderManager({
        ...Repo.getShaderManager(),
        programMap:
          _getProgramMap(Repo.getShaderManager())
          //这里也使用基础设施层的“数据”的ImmutableHashMap,因为操作的是ShaderManager PO的programMap
          |> ImmutableHashMap.set(shaderId, program),
      });
    };
    

    10、修改CreateRepo.re,实现创建ShaderManager PO的programMap字段
    CreateRepo.re相关代码为:

    let create = () => {
      ...
      shaderManager: {
        ...
        programMap: ImmutableHashMap.createEmpty(),
      },
    };
    

    实现用户代码并运行测试

    1、在项目根目录上执行webpack命令,更新wd.js文件

    yarn webpack
    

    2、实现index.html相关代码

    index.html代码为:

      <script>
        ...
        //准备webgl上下文的配置项
        var contextConfig = {
          "alpha": true,
          "depth": true,
          "stencil": false,
          "antialias": true,
          "premultipliedAlpha": true,
          "preserveDrawingBuffer": false,
        };
    
        wd.Director.init(contextConfig);
      </script>
    

    3、运行测试

    运行index.html页面

    打开控制台,可以看到打印了两次数组,每次数组内容为[Shader名称, vs, fs],其中第一次的Shader名称为“shader2”,第二次为“shader1”

  • 相关阅读:
    影响STA的因素-OCV
    FPGA的可靠性分析
    DFT
    Verilog 延时模型
    收缩数据库日志
    iis设置局域网访问,Context.Request.Url.Authority老是取出为localhost问题
    vs2012 后期生成事件命令报错 9009
    MIME配置
    sql 字符串拼接 =>for xml()
    js 切换embed的src值
  • 原文地址:https://www.cnblogs.com/chaogex/p/12418289.html
Copyright © 2011-2022 走看看