shaman.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package renderer
  5. import (
  6. "bytes"
  7. "fmt"
  8. "text/template"
  9. "github.com/g3n/engine/gls"
  10. "github.com/g3n/engine/material"
  11. "github.com/g3n/engine/renderer/shader"
  12. )
  13. type ShaderSpecs struct {
  14. Name string // Shader name
  15. Version string // GLSL version
  16. ShaderUnique bool // indicates if shader is independent of lights and textures
  17. UseLights material.UseLights // Bitmask indicating which lights to consider
  18. AmbientLightsMax int // Current number of ambient lights
  19. DirLightsMax int // Current Number of directional lights
  20. PointLightsMax int // Current Number of point lights
  21. SpotLightsMax int // Current Number of spot lights
  22. MatTexturesMax int // Current Number of material textures
  23. }
  24. type ProgSpecs struct {
  25. program *gls.Program // program object
  26. specs ShaderSpecs // associated specs
  27. }
  28. type Shaman struct {
  29. gs *gls.GLS
  30. chunks *template.Template // template with all chunks
  31. shaders map[string]*template.Template // maps shader name to its template
  32. proginfo map[string]shader.ProgramInfo // maps name of the program to ProgramInfo
  33. programs []ProgSpecs // list of compiled programs with specs
  34. specs ShaderSpecs // Current shader specs
  35. }
  36. // NewShaman creates and returns a pointer to a new shader manager
  37. func NewShaman(gs *gls.GLS) *Shaman {
  38. sm := new(Shaman)
  39. sm.Init(gs)
  40. return sm
  41. }
  42. // Init initializes the shander manager
  43. func (sm *Shaman) Init(gs *gls.GLS) {
  44. sm.gs = gs
  45. sm.chunks = template.New("_chunks_")
  46. sm.shaders = make(map[string]*template.Template)
  47. sm.proginfo = make(map[string]shader.ProgramInfo)
  48. // Add "loop" function to chunks template
  49. // "loop" is used inside the shader templates to unroll loops.
  50. sm.chunks.Funcs(template.FuncMap{
  51. "loop": func(n int) []int {
  52. s := make([]int, n)
  53. for i := range s {
  54. s[i] = i
  55. }
  56. return s
  57. },
  58. })
  59. }
  60. // AddDefaultShaders adds to the shader manager all default
  61. // shaders statically registered.
  62. func (sm *Shaman) AddDefaultShaders() error {
  63. for name, source := range shader.Chunks() {
  64. err := sm.AddChunk(name, source)
  65. if err != nil {
  66. return err
  67. }
  68. }
  69. for name, source := range shader.Shaders() {
  70. err := sm.AddShader(name, source)
  71. if err != nil {
  72. return err
  73. }
  74. }
  75. for name, pinfo := range shader.Programs() {
  76. sm.proginfo[name] = pinfo
  77. }
  78. return nil
  79. }
  80. // AddChunk adds a shader chunk with the specified name and source code
  81. func (sm *Shaman) AddChunk(name, source string) error {
  82. tmpl := sm.chunks.New(name)
  83. _, err := tmpl.Parse(source)
  84. if err != nil {
  85. return err
  86. }
  87. return nil
  88. }
  89. // AddShader adds a shader program with the specified name and source code
  90. func (sm *Shaman) AddShader(name, source string) error {
  91. // Clone chunks template so any shader can use
  92. // any of the chunks
  93. tmpl, err := sm.chunks.Clone()
  94. if err != nil {
  95. return err
  96. }
  97. // Parses this shader template source
  98. _, err = tmpl.Parse(source)
  99. if err != nil {
  100. return err
  101. }
  102. sm.shaders[name] = tmpl
  103. return nil
  104. }
  105. // AddProgram adds a program with the specified name and associated vertex
  106. // and fragment shaders names (previously registered)
  107. // To specify other types of shaders for a program use SetProgramShader()
  108. func (sm *Shaman) AddProgram(name, vertexName, fragName string) error {
  109. sm.proginfo[name] = shader.ProgramInfo{Vertex: vertexName, Frag: fragName}
  110. return nil
  111. }
  112. // SetProgramShader sets the shader type and name for a previously specified program name.
  113. // Returns error if the specified program or shader name not found or
  114. // if an invalid shader type was specified.
  115. func (sm *Shaman) SetProgramShader(pname string, stype int, sname string) error {
  116. // Checks if program name is valid
  117. pinfo, ok := sm.proginfo[pname]
  118. if !ok {
  119. return fmt.Errorf("Program name:%s not found", pname)
  120. }
  121. // Checks if shader name is valid
  122. _, ok = sm.shaders[sname]
  123. if !ok {
  124. return fmt.Errorf("Shader name:%s not found", sname)
  125. }
  126. // Sets the program shader name for the specified type
  127. switch stype {
  128. case gls.VERTEX_SHADER:
  129. pinfo.Vertex = sname
  130. case gls.FRAGMENT_SHADER:
  131. pinfo.Frag = sname
  132. case gls.GEOMETRY_SHADER:
  133. pinfo.Geometry = sname
  134. default:
  135. return fmt.Errorf("Invalid shader type")
  136. }
  137. return nil
  138. }
  139. // SetProgram set the shader program to satisfy the specified specs.
  140. // Returns an indication if the current shader has changed and a possible error
  141. // when creating a new shader program.
  142. // Receives a copy of the specs because it changes the fields which specify the
  143. // number of lights depending on the UseLights flags.
  144. func (sm *Shaman) SetProgram(s *ShaderSpecs) (bool, error) {
  145. // Checks material use lights bit mask
  146. specs := *s
  147. if (specs.UseLights & material.UseLightAmbient) == 0 {
  148. specs.AmbientLightsMax = 0
  149. }
  150. if (specs.UseLights & material.UseLightDirectional) == 0 {
  151. specs.DirLightsMax = 0
  152. }
  153. if (specs.UseLights & material.UseLightPoint) == 0 {
  154. specs.PointLightsMax = 0
  155. }
  156. if (specs.UseLights & material.UseLightSpot) == 0 {
  157. specs.SpotLightsMax = 0
  158. }
  159. // If current shader specs are the same as the specified specs, nothing to do.
  160. if sm.specs.Compare(&specs) {
  161. return false, nil
  162. }
  163. // Search for compiled program with the specified specs
  164. for _, pinfo := range sm.programs {
  165. if pinfo.specs.Compare(&specs) {
  166. sm.gs.UseProgram(pinfo.program)
  167. sm.specs = specs
  168. return true, nil
  169. }
  170. }
  171. // Generates new program with the specified specs
  172. prog, err := sm.GenProgram(&specs)
  173. if err != nil {
  174. return false, err
  175. }
  176. log.Debug("Created new shader:%v", specs.Name)
  177. // Save specs as current specs, adds new program to the list
  178. // and actives program
  179. sm.specs = specs
  180. sm.programs = append(sm.programs, ProgSpecs{prog, specs})
  181. sm.gs.UseProgram(prog)
  182. return true, nil
  183. }
  184. // Generates shader program from the specified specs
  185. func (sm *Shaman) GenProgram(specs *ShaderSpecs) (*gls.Program, error) {
  186. // Get info for the specified shader program
  187. progInfo, ok := sm.proginfo[specs.Name]
  188. if !ok {
  189. return nil, fmt.Errorf("Program:%s not found", specs.Name)
  190. }
  191. // Sets the GLSL version string
  192. specs.Version = "330 core"
  193. // Get vertex shader compiled template
  194. vtempl, ok := sm.shaders[progInfo.Vertex]
  195. if !ok {
  196. return nil, fmt.Errorf("Vertex shader:%s template not found", progInfo.Vertex)
  197. }
  198. // Generates vertex shader source from template
  199. var sourceVertex bytes.Buffer
  200. err := vtempl.Execute(&sourceVertex, specs)
  201. if err != nil {
  202. return nil, err
  203. }
  204. // Get fragment shader compiled template
  205. fragTempl, ok := sm.shaders[progInfo.Frag]
  206. if !ok {
  207. return nil, fmt.Errorf("Fragment shader:%s template not found", progInfo.Frag)
  208. }
  209. // Generates fragment shader source from template
  210. var sourceFrag bytes.Buffer
  211. err = fragTempl.Execute(&sourceFrag, specs)
  212. if err != nil {
  213. return nil, err
  214. }
  215. // Checks for optional geometry shader compiled template
  216. var sourceGeom bytes.Buffer
  217. if progInfo.Geometry != "" {
  218. // Get geometry shader compiled template
  219. geomTempl, ok := sm.shaders[progInfo.Geometry]
  220. if !ok {
  221. return nil, fmt.Errorf("Geometry shader:%s template not found", progInfo.Geometry)
  222. }
  223. // Generates geometry shader source from template
  224. err = geomTempl.Execute(&sourceGeom, specs)
  225. if err != nil {
  226. return nil, err
  227. }
  228. }
  229. // Creates shader program
  230. prog := sm.gs.NewProgram()
  231. prog.AddShader(gls.VERTEX_SHADER, sourceVertex.String(), nil)
  232. prog.AddShader(gls.FRAGMENT_SHADER, sourceFrag.String(), nil)
  233. if progInfo.Geometry != "" {
  234. prog.AddShader(gls.GEOMETRY_SHADER, sourceGeom.String(), nil)
  235. }
  236. err = prog.Build()
  237. if err != nil {
  238. return nil, err
  239. }
  240. return prog, nil
  241. }
  242. // Compare compares two shaders specifications structures
  243. func (ss *ShaderSpecs) Compare(other *ShaderSpecs) bool {
  244. if ss.Name != other.Name {
  245. return false
  246. }
  247. if other.ShaderUnique {
  248. return true
  249. }
  250. if ss.AmbientLightsMax == other.AmbientLightsMax &&
  251. ss.DirLightsMax == other.DirLightsMax &&
  252. ss.PointLightsMax == other.PointLightsMax &&
  253. ss.SpotLightsMax == other.SpotLightsMax &&
  254. ss.MatTexturesMax == other.MatTexturesMax {
  255. return true
  256. }
  257. return false
  258. }