目录
上一篇博文
下一篇博文
继续实现
实现 "DirectorJsAPI.init"
实现 "保存 WebGL 上下文" 限界上下文
实现 "初始化所有 Shader" 限界上下文
实现用户代码并运行测试
大家好, 本文根据领域驱动设计的成果, 实现了 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"
来源: https://www.cnblogs.com/chaogex/p/12418289.html