GLUtils.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. unit GLUtils;
  2. interface
  3. uses
  4. MemoryBuffer, Mat4, GLTypes,
  5. BrowserConsole, WebGL, JS,
  6. Types, SysUtils;
  7. type
  8. TShader = class
  9. public
  10. constructor Create (context: TJSWebGLRenderingContext; vertexShaderSource, fragmentShaderSource: string);
  11. procedure Compile;
  12. procedure Link;
  13. procedure Use;
  14. function GetAttribLocation (name: string): GLint;
  15. procedure BindAttribLocation (index: GLuint; name: string);
  16. procedure SetUniformMat4 (name: string; value: TMat4);
  17. procedure SetUniformVec3 (name: string; value: TVec3);
  18. procedure SetUniformFloat (name: string; value: GLfloat);
  19. private
  20. gl: TJSWebGLRenderingContext;
  21. vertexShader: TJSWebGLShader;
  22. fragmentShader: TJSWebGLShader;
  23. programID: TJSWebGLProgram;
  24. function GetUniformLocation (name: string): TJSWebGLUniformLocation;
  25. function CreateShader (theType: GLenum; source: string): TJSWebGLShader;
  26. end;
  27. type
  28. TModelData = record
  29. verticies: TJSFloat32Array; // GLfloat
  30. // NOTE: it's not clear if WebGL supports GLuint
  31. // https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements
  32. indicies: TJSUint16Array; // GLushort
  33. floatsPerVertex: integer;
  34. end;
  35. const
  36. kModelVertexFloats = 3 + 2 + 3;
  37. type
  38. TModelVertex = record
  39. pos: TVec3;
  40. texCoord: TVec2;
  41. normal: TVec3;
  42. end;
  43. procedure ModelVertexAddToBuffer(vertex: TModelVertex; buffer: TMemoryBuffer);
  44. procedure ModelVertexAddToArray (vertex: TModelVertex; list: TJSArray);
  45. type
  46. TModel = class
  47. public
  48. constructor Create(context: TJSWebGLRenderingContext; modelData: TModelData); overload;
  49. procedure Draw;
  50. private
  51. gl: TJSWebGLRenderingContext;
  52. data: TModelData;
  53. vertexBuffer: TJSWebGLBuffer;
  54. indexBuffer: TJSWebGLBuffer;
  55. //elementCount: integer;
  56. procedure EnableAttributes;
  57. procedure Load;
  58. end;
  59. function LoadOBJFile (text: TJSString): TModelData;
  60. function GLSizeof(glType: NativeInt): integer;
  61. procedure GLFatal (gl: TJSWebGLRenderingContext; messageString: string = 'Fatal OpenGL error');
  62. implementation
  63. {=============================================}
  64. {@! ___Utilities___ }
  65. {=============================================}
  66. procedure Fatal (messageString: string); overload;
  67. begin
  68. writeln('*** FATAL: ', messageString);
  69. raise Exception.Create('FATAL');
  70. end;
  71. // TODO: toll free bridge to FPC strings
  72. {procedure Fatal (messageString: TJSString); overload;
  73. begin
  74. writeln('*** FATAL: ', messageString);
  75. raise Exception.Create('FATAL');
  76. end;}
  77. procedure GLFatal (gl: TJSWebGLRenderingContext; messageString: string = 'Fatal OpenGL error');
  78. var
  79. error: integer;
  80. begin
  81. error := gl.getError();
  82. if error <> TJSWebGLRenderingContext.NO_ERROR then
  83. begin
  84. // TODO: case doesn't work?
  85. case error of
  86. TJSWebGLRenderingContext.INVALID_VALUE:
  87. messageString := messageString+' (GL_INVALID_VALUE)';
  88. TJSWebGLRenderingContext.INVALID_OPERATION:
  89. messageString := messageString+' (GL_INVALID_OPERATION)';
  90. TJSWebGLRenderingContext.INVALID_ENUM:
  91. messageString := messageString+' (GL_INVALID_ENUM)';
  92. otherwise
  93. messageString := messageString+' '+IntToStr(error);
  94. end;
  95. Fatal(messageString);
  96. end;
  97. end;
  98. function GLSizeof(glType: NativeInt): integer;
  99. begin
  100. case glType of
  101. TJSWebGLRenderingContext.UNSIGNED_BYTE, TJSWebGLRenderingContext.BYTE:
  102. result := 1;
  103. TJSWebGLRenderingContext.SHORT, TJSWebGLRenderingContext.UNSIGNED_SHORT:
  104. result := 2;
  105. TJSWebGLRenderingContext.INT, TJSWebGLRenderingContext.UNSIGNED_INT:
  106. result := 4;
  107. TJSWebGLRenderingContext.FLOAT:
  108. result := 4;
  109. otherwise
  110. Fatal('GLSizeof type is invalid.');
  111. end;
  112. end;
  113. {=============================================}
  114. {@! ___Model___ }
  115. {=============================================}
  116. procedure ModelVertexAddToBuffer(vertex: TModelVertex; buffer: TMemoryBuffer);
  117. begin
  118. buffer.AddFloats(kModelVertexFloats, [
  119. vertex.pos.x, vertex.pos.y, vertex.pos.z,
  120. vertex.texCoord.x, vertex.texCoord.y,
  121. vertex.normal.x, vertex.normal.y, vertex.normal.z
  122. ]);
  123. end;
  124. procedure ModelVertexAddToArray (vertex: TModelVertex; list: TJSArray);
  125. begin
  126. list.push(vertex.pos.x);
  127. list.push(vertex.pos.y);
  128. list.push(vertex.pos.z);
  129. list.push(vertex.texCoord.x);
  130. list.push(vertex.texCoord.y);
  131. list.push(vertex.normal.x);
  132. list.push(vertex.normal.y);
  133. list.push(vertex.normal.z);
  134. end;
  135. constructor TModel.Create(context: TJSWebGLRenderingContext; modelData: TModelData);
  136. begin
  137. gl := context;
  138. data := modelData;
  139. Load;
  140. end;
  141. procedure TModel.Draw;
  142. begin
  143. gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  144. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  145. EnableAttributes;
  146. gl.drawElements(gl.TRIANGLES, data.indicies.length, gl.UNSIGNED_SHORT, 0);
  147. gl.bindBuffer(gl.ARRAY_BUFFER, nil);
  148. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, nil);
  149. end;
  150. procedure TModel.EnableAttributes;
  151. var
  152. offset: integer;
  153. stride: integer;
  154. begin
  155. // NOTE: we don't have VAO's yet so we need to enable vertex attributes for shader
  156. // before every draw call (unless the array buffer hasn't changed between calls)
  157. offset := 0;
  158. stride := data.floatsPerVertex * GLSizeof(TJSWebGLRenderingContext.FLOAT);
  159. // position
  160. gl.enableVertexAttribArray(0);
  161. gl.vertexAttribPointer(0, 3, gl.FLOAT, false, stride, offset);
  162. offset += GLSizeof(TJSWebGLRenderingContext.FLOAT) * 3;
  163. // texture
  164. gl.enableVertexAttribArray(1);
  165. gl.vertexAttribPointer(1, 2, gl.FLOAT, false, stride, offset);
  166. offset += GLSizeof(TJSWebGLRenderingContext.FLOAT) * 2;
  167. // normal
  168. gl.enableVertexAttribArray(2);
  169. gl.vertexAttribPointer(2, 3, gl.FLOAT, false, stride, offset);
  170. offset += GLSizeof(TJSWebGLRenderingContext.FLOAT) * 3;
  171. end;
  172. procedure TModel.Load;
  173. begin
  174. indexBuffer := gl.createBuffer;
  175. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  176. gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indicies, gl.STATIC_DRAW);
  177. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, nil);
  178. vertexBuffer := gl.createBuffer;
  179. gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
  180. gl.bufferData(gl.ARRAY_BUFFER, data.verticies, gl.STATIC_DRAW);
  181. gl.bindBuffer(gl.ARRAY_BUFFER, nil);
  182. end;
  183. type
  184. TOBJVertex = record
  185. position: TVec3;
  186. textureIndex: integer;
  187. normalIndex: integer;
  188. end;
  189. function ProcessFace (verticies: TJSArray; indices: TJSArray; face: TStringDynArray): TOBJVertex;
  190. var
  191. index: integer;
  192. vertex: TOBJVertex;
  193. begin
  194. index := StrToInt(face[0]) - 1;
  195. vertex := TOBJVertex(verticies[index]);
  196. // NOTE: see TOBJData
  197. // index can't exceed GLushort
  198. if index > 65536 then
  199. Fatal('overflowed indices array');
  200. if face[1] <> '' then
  201. vertex.textureIndex := StrToInt(face[1]) - 1
  202. else
  203. vertex.textureIndex := -1;
  204. if face[2] <> '' then
  205. vertex.normalIndex := StrToInt(face[2]) - 1
  206. else
  207. vertex.normalIndex := -1;
  208. indices.push(index);
  209. verticies[index] := vertex;
  210. result := vertex;
  211. end;
  212. function LoadOBJFile (text: TJSString): TModelData;
  213. const
  214. kLineEnding = #10;
  215. kSpace = ' '; // what code is space?
  216. var
  217. lines: TStringDynArray;
  218. parts: TStringDynArray;
  219. indices: TJSArray;
  220. positions: TJSArray;
  221. normals: TJSArray;
  222. textures: TJSArray;
  223. verticies: TJSArray;
  224. mesh: TJSFloat32Array;
  225. i: integer;
  226. line: TJSString;
  227. vertex: TOBJVertex;
  228. vertexIndex: integer;
  229. data: TModelData;
  230. pos: TVec3;
  231. texCoord: TVec2;
  232. normal: TVec3;
  233. begin
  234. positions := TJSArray.new;
  235. normals := TJSArray.new;
  236. textures := TJSArray.new;
  237. indices := TJSArray.new;
  238. verticies := TJSArray.new;
  239. lines := text.split(kLineEnding);
  240. for i := 0 to high(lines) do
  241. begin
  242. line := TJSString(lines[i]);
  243. parts := line.split(kSpace);
  244. if line.startsWith('v ') then
  245. begin
  246. pos := V3(StrToFloat(parts[1]), StrToFloat(parts[2]), StrToFloat(parts[3]));
  247. positions.push(pos);
  248. // add new vertex
  249. vertex.position := pos;
  250. vertex.textureIndex := -1;
  251. vertex.normalIndex := -1;
  252. verticies.push(vertex);
  253. end
  254. else if line.startsWith('vn ') then
  255. begin
  256. normals.push(V3(StrToFloat(parts[1]), StrToFloat(parts[2]), StrToFloat(parts[3])));
  257. end
  258. else if line.startsWith('vt ') then
  259. begin
  260. textures.push(V2(StrToFloat(parts[1]), 1 - StrToFloat(parts[2])));
  261. end
  262. else if line.startsWith('f ') then
  263. begin
  264. ProcessFace(verticies, indices, TJSString(parts[1]).split('/'));
  265. ProcessFace(verticies, indices, TJSString(parts[2]).split('/'));
  266. ProcessFace(verticies, indices, TJSString(parts[3]).split('/'));
  267. end;
  268. end;
  269. // vec3 (position) + vec2 (texCoord) + vec3 (normal)
  270. data.floatsPerVertex := kModelVertexFloats;
  271. mesh := TJSFloat32Array.New(data.floatsPerVertex * verticies.length);
  272. for i := 0 to verticies.length - 1 do
  273. begin
  274. vertex := TOBJVertex(verticies[i]);
  275. vertexIndex := i * data.floatsPerVertex;
  276. // position
  277. pos := TVec3(positions[i]);
  278. mesh[vertexIndex + 0] := pos.x;
  279. mesh[vertexIndex + 1] := pos.y;
  280. mesh[vertexIndex + 2] := pos.z;
  281. // texture
  282. if vertex.textureIndex <> -1 then
  283. begin
  284. texCoord := TVec2(textures[vertex.textureIndex]);
  285. mesh[vertexIndex + 3] := texCoord.x;
  286. mesh[vertexIndex + 4] := texCoord.y;
  287. end
  288. else
  289. begin
  290. mesh[vertexIndex + 3] := 0;
  291. mesh[vertexIndex + 4] := 0;
  292. end;
  293. // normal
  294. if vertex.normalIndex <> -1 then
  295. begin
  296. normal := TVec3(normals[vertex.normalIndex]);
  297. mesh[vertexIndex + 5] := normal.x;
  298. mesh[vertexIndex + 6] := normal.y;
  299. mesh[vertexIndex + 7] := normal.z;
  300. end;
  301. end;
  302. //writeln('floats: ', mesh.length);
  303. //writeln('positions:', positions.length);
  304. //writeln('indices:', indices.length);
  305. data.verticies := mesh;
  306. data.indicies := TJSUint16Array.New(TJSObject(indices));
  307. result := data;
  308. end;
  309. {=============================================}
  310. {@! ___Shader___ }
  311. {=============================================}
  312. function TShader.GetUniformLocation (name: string): TJSWebGLUniformLocation;
  313. begin
  314. // TODO: cache these. how do we use dictionarys from JS in Pascal?
  315. result := gl.getUniformLocation(programID, name);
  316. GLFatal(gl, 'gl.getUniformLocation');
  317. end;
  318. procedure TShader.SetUniformFloat (name: string; value: GLfloat);
  319. begin
  320. gl.uniform1f(GetUniformLocation(name), value);
  321. GLFatal(gl, 'gl.uniform1f');
  322. end;
  323. procedure TShader.SetUniformVec3 (name: string; value: TVec3);
  324. begin
  325. //gl.uniform3fv(GetUniformLocation(name), ToFloats(value));
  326. gl.uniform3f(GetUniformLocation(name), value.x, value.y, value.z);
  327. GLFatal(gl, 'gl.uniform3fv');
  328. end;
  329. procedure TShader.SetUniformMat4 (name: string; value: TMat4);
  330. var
  331. list: TJSFloat32List;
  332. begin
  333. // TODO: fix mat4 to use flat arrays
  334. list := TJSFloat32List(value.CopyList);
  335. gl.uniformMatrix4fv(GetUniformLocation(name), false, list);
  336. GLFatal(gl, 'gl.uniformMatrix4fv');
  337. end;
  338. function TShader.GetAttribLocation (name: string): GLint;
  339. begin
  340. result := gl.getAttribLocation(programID, name);
  341. end;
  342. procedure TShader.BindAttribLocation (index: GLuint; name: string);
  343. begin
  344. gl.bindAttribLocation(programID, index, name);
  345. //GLFatal('glBindAttribLocation '+IntToStr(index)+':'+name);
  346. end;
  347. constructor TShader.Create (context: TJSWebGLRenderingContext; vertexShaderSource, fragmentShaderSource: string);
  348. begin
  349. gl := context;
  350. vertexShader := CreateShader(gl.VERTEX_SHADER, vertexShaderSource);
  351. fragmentShader := CreateShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
  352. end;
  353. function TShader.CreateShader(theType: GLenum; source: string): TJSWebGLShader;
  354. begin
  355. Result := gl.createShader(theType);
  356. if Result = nil then
  357. Fatal('create shader failed');
  358. gl.shaderSource(Result, source);
  359. gl.compileShader(Result);
  360. if gl.getShaderParameter(Result, gl.COMPILE_STATUS) then
  361. begin
  362. //writeln('loaded shader ', theType);
  363. exit;
  364. end
  365. else
  366. begin
  367. Fatal(gl.getShaderInfoLog(Result));
  368. //gl.deleteShader(shader);
  369. end;
  370. end;
  371. procedure TShader.Compile;
  372. begin
  373. programID := gl.createProgram;
  374. gl.attachShader(programID, vertexShader);
  375. gl.attachShader(programID, fragmentShader);
  376. end;
  377. procedure TShader.Link;
  378. begin
  379. gl.linkProgram(programID);
  380. if not gl.getProgramParameter(programID, gl.LINK_STATUS) then
  381. begin
  382. Fatal(gl.getProgramInfoLog(programID));
  383. //gl.deleteProgram(programID);
  384. end;
  385. end;
  386. procedure TShader.Use;
  387. begin
  388. gl.useProgram(programID);
  389. end;
  390. end.