1%%
2%%  auv_texture.erl --
3%%
4%%     Render and capture a texture.
5%%
6%%  Copyright (c) 2002-2011 Dan Gudmundsson, Bjorn Gustavsson
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11%%     $Id$
12%%
13
14-module(auv_texture).
15-export([get_texture/2,draw_options/1,delete_preview_vbo/0]).
16
17-define(NEED_OPENGL, 1).
18-define(NEED_ESDL, 1).
19-define(ERROR, error_msg(?LINE)).
20-include_lib("src/wings.hrl").
21-include_lib("e3d/e3d_image.hrl").
22-include_lib("e3d/e3d.hrl").
23-include("auv.hrl").
24
25-import(lists, [foreach/2,reverse/1,sort/1,foldl/3,member/2]).
26-import(auv_segment, [map_vertex/2]).
27
28-define(OPT_BG, [auv_background, {type_sel,color},{undefined,ignore},{1.0,1.0,1.0,0.0}]).
29-define(OPT_EDGES, [auv_edges, all_edges,{0.0,0.0,0.0},1.0,false]).
30-define(OPT_FACES, [texture]).
31-define(PREVIEW_SIZE, 256).
32-define(SHADER_PRW_NAME, "shdr_preview").
33-define(SHADER_PRW_SIZE, 512).
34-define(SHADER_PRW_VBO, shdr_vbo).
35
36-record(opt, {texsz = {512,512},   %% Texture size
37	      no_renderers = 4,
38	      renderers = [{auv_background, ?OPT_BG},
39			   {auv_edges, [?OPT_EDGES]}]
40	     }).
41
42-record(sh, {id=ignore,
43	     name="Unnamed",   %% Shader menu entry
44	     file="",
45	     vs = "",          %% Vertex shader
46	     fs = "",          %% Fragment shader
47	     tex_units = 1,    %% No of texture units used
48	     reqs = [],        %% Requirements: normals and/or binormals
49	     args = [],        %% Arguments
50	     def  = [],        %% Gui Strings
51	     preview = true    %% Shows or not the preview dialog for the shader
52	    }).
53
54-record(sh_conf, {texsz,      % More shader options
55		  fbo_r,      % Fbo read buffer
56		  fbo_w,      % Fbo write buffer
57		  fbo_d,      % Clean up fbo
58		  prog,       % Shader Id
59		  ts}).       % Shader data
60
61-record(ts,         % What              Type
62	{charts,    % #chart{}          (list)
63         uv,        % UV positions       (binary)
64	 pos,       % Real 3D position  (binary)
65 	 n,         % Normal            (binary) Optional
66	 bi,        % BiNormal          (binary) Optional
67	 bb,        % BoundingBox 3D pos
68         vc,        % Vertex colors     (binary)
69         vbo        % Vbo               (binary)
70	}).
71
72-record(chart,
73	{id,        % Chart ID
74	 fs,        % Faces see [{Face,#fs{}}]
75	 oes=[],    % Outer vertices [[va,vb,face],[vc,vb,face]..],
76	 bb_uv,     % Uv Bounding Box {{minX,minY,0},{maxX,maxY,0}}
77	 mode       % Previous mode material/vertex-colored
78	}).
79
80-record(fs,
81	{vs,        % triangulated vertex id in uv window [[Id1,Id2,Id3]]
82	 vse,       % face vertex id's untriangulated for edge drawings
83	 id}).      % Face Id
84
85%% Menu
86
87draw_options(#st{bb=Uvs}=AuvSt0) ->
88    #uvstate{st=GeomSt0,matname=MatName0,bg_img=TexImg} = Uvs,
89    BkpImg = wings_image:info(TexImg),
90    prw_img_id(new),
91
92    [MaxTxs0|_] = gl:getIntegerv(?GL_MAX_TEXTURE_SIZE),
93    MaxTxs = max(min(8192, MaxTxs0), 256),
94    Shaders = shaders(),
95    Prefs = get_valid_prefs(Shaders),
96    TexSz = proplists:get_value(texsz, Prefs, 512),
97    Qs = [{hframe,[{menu, gen_tx_sizes(MaxTxs, []),TexSz,
98		    [{key,texsz}]}],[{title,?__(1,"Size")}]},
99	  {vframe, render_passes(Prefs, Shaders), [{title,?__(2,"Render")}]}
100	 ],
101    wings_dialog:dialog(?__(3,"Draw Options"), {preview,Qs},
102			fun({dialog_preview,Options}) ->
103                Opt = list_to_prefs(Options),
104                NewImg = ?SLOW(get_texture(AuvSt0, {Opt,Shaders})),
105                case MatName0 of
106                    none ->
107                        ok = wings_image:update(TexImg, NewImg);
108                    _ ->
109                        TexName = case get_mat_texture(MatName0, GeomSt0) of
110                                      false -> atom_to_list(MatName0);
111                                      OldId  ->
112                                          OldImg = wings_image:info(OldId),
113                                          case OldImg#e3d_image.name of
114                                              "auvBG" -> atom_to_list(MatName0);
115                                              Other -> Other
116                                          end
117                                  end,
118                        catch wings_material:update_image(MatName0, diffuse, NewImg#e3d_image{name=TexName}, GeomSt0)
119                end,
120                {preview,GeomSt0,GeomSt0};
121            (cancel) ->
122                case MatName0 of
123                    none ->
124                        ok = wings_image:update(TexImg, BkpImg);
125                    _ ->
126                        catch wings_material:update_image(MatName0, diffuse, BkpImg, GeomSt0)
127                end,
128                wings_wm:later({new_state,AuvSt0}),
129                prw_img_id(delete),
130                GeomSt0;
131            (Options) ->
132                Opt = list_to_prefs(Options),
133                prw_img_id(delete),
134                set_pref([{tx_prefs,pref_to_list(Opt)}]),
135                {auv,{draw_options,{Opt,Shaders}}}
136			end).
137
138get_mat_texture(MatName, #st{mat=Materials}) ->
139    get_mat_texture(MatName, Materials);
140get_mat_texture(MatName, Materials) ->
141    case gb_trees:lookup(MatName, Materials) of
142        none -> false;
143        {value,Mat} ->
144            Maps = proplists:get_value(maps, Mat, []),
145            proplists:get_value(diffuse, Maps, false)
146    end.
147
148%% the goal is to remove invalid images references from the previous
149%% shader settings stored in preferences which may not be present in
150%% the current project. That avoid crashes on preview dialg
151get_valid_prefs(Shaders) ->
152    Prefs = get_pref(tx_prefs, pref_to_list(#opt{})),
153    validate_prefs(Prefs,Shaders,[]).
154
155validate_prefs([], _, Acc) -> Acc;
156validate_prefs([{texsz,_}=Val|Prefs], Sh, Acc) ->
157    validate_prefs(Prefs, Sh, [Val|Acc]);
158validate_prefs([{{auv_pass,_}=Slot,PassId}=Pass,{{auv_opt,_}=Op,OptVal}=Opt0|Prefs], Sh, Acc) ->
159    PassOpt =
160        case PassId of
161            {shader,Id} when OptVal /= [] ->
162                case lists:keysearch(Id,#sh.id,Sh) of
163                    {value,#sh{args=Args}} ->
164                        [{shader,_}|Opts] = OptVal,
165                        case valid_opt(reverse(Args),Opts,true) of
166                            true -> [Pass,Opt0];
167                            false -> [{Slot,ignore},{Op,[]}]
168                        end;
169                    _ -> [{Slot,ignore},{Op,[]}]
170                end;
171            _ -> [Pass,Opt0]
172        end,
173    validate_prefs(Prefs, Sh, Acc++PassOpt);
174validate_prefs([Val|Prefs], Sh, Acc) ->
175    validate_prefs(Prefs, Sh, [Val|Acc]).
176
177valid_opt([], _, Acc) -> Acc;
178valid_opt([{uniform,{image,_},_,_,_}|As], [{_,Id}|Opts], Acc) ->
179    ValidImg = wings_image:txid(Id) /= none,
180    valid_opt(As,Opts,Acc and ValidImg);
181valid_opt([{uniform,_,_,_,_}|As], [_|Opts], Acc) ->
182    valid_opt(As,Opts,Acc);
183valid_opt([_|As], Opts, Acc) ->
184    valid_opt(As,Opts,Acc).
185
186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187%% Menu handling
188%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
189
190render_passes(Prefs, Shaders) ->
191    NoOfPasses = 7,
192    Menu = renderers(Shaders),
193    Background =
194	{hframe,
195	 [{menu,[{?__(1,"Background"), auv_background}],auv_background,
196	   [{key,{auv_pass,0}}, enable_opt()]},
197	  {value, get_def(Prefs,auv_opt,0), [{key,{auv_opt,0}}]},
198	  {button,?__(2,"Options"),keep,
199	   [{key, {opt_butt, 0}}, option_hook(0,background(),[])]}],
200	 []},
201    Other = [{hframe,
202	      [{menu,Menu,default_menu(Id, Prefs),
203		[{key,{auv_pass,Id}}, enable_opt()]},
204	       {value,get_def(Prefs, auv_opt, Id),[{key,{auv_opt,Id}}]},
205	       {button,?__(3,"Options"),keep,
206		[{key, {opt_butt, Id}}, option_hook(Id, Menu, Shaders)]}
207	      ],
208	      []} || Id <- lists:seq(1, NoOfPasses)],
209    [Background|Other].
210
211default_menu(Pass, Prefs) ->
212    case get_def(Prefs, auv_pass, Pass) of
213	ignore -> default_menu(Pass);
214	Val -> Val
215    end.
216
217default_menu(1) -> auv_edges;
218default_menu(_) -> ignore.
219
220get_def(List, What, Id) ->
221    case proplists:get_value({What,Id}, List) of
222	undefined when What =:= auv_pass -> ignore;
223	undefined when What =:= auv_opt  -> [];
224	Val -> Val
225    end.
226
227background() ->
228    [{?__(1,"Background"), auv_background}].
229renderers(Shaders) ->
230    Menu0 = [{"*"++Name++"*",{shader,Id}} ||
231		#sh{name=Name,id=Id} <- Shaders],
232    [{?__(1,"None"), ignore},
233     {?__(2,"Draw Edges"),auv_edges},
234     {?__(3,"Draw Faces"),auv_faces}|Menu0].
235
236option_dialog(Id, Fields, Renderers, Shaders) ->
237    try
238        Name = wings_dialog:get_value({auv_pass, Id}, Fields),
239        Opts =
240            case wings_dialog:get_value({auv_opt, Id}, Fields) of
241                [] ->  %% it happens first time the menu item is filled
242                    %% we need the Vals field to be filled in the next dialog,
243                    %% then we need to build it at this point
244                    case Name of
245                        Name when is_atom(Name) -> NameId = Name;
246                        {_,NameId} -> NameId
247                    end,
248                    if NameId =/= auv_edges ->
249                        {value,#sh{def=Opts0}} = lists:keysearch(NameId,#sh.id,Shaders),
250                        [{shader,NameId}|lists:reverse(Opts0)];
251                    true ->
252                        []
253                    end;
254                Opts0 ->
255                    Opts0
256            end,
257        {StrName, Name} = renderer(Name,Renderers),
258        SetValue = fun(Res) ->
259                       %% Change the value in the parent dialog
260                       wings_dialog:set_value({auv_opt, Id}, [Name|Res], Fields),
261                       %% removing the temporary VBO data
262                       delete_preview_vbo()
263                   end,
264        SphereData = create_sphere_data(),
265        wings_dialog:dialog(StrName,options(Name,Opts,Shaders,SphereData),SetValue)
266    catch _:Crash:ST ->
267	    io:format("EXIT: ~p ~p~n",[Crash, ST])
268    end.
269
270%% removing the temporary VBO data used by preview
271delete_preview_vbo() ->
272    case wings_pref:get_value(?SHADER_PRW_VBO) of
273        undefined ->  ignore;
274        Vbo ->
275            wings_pref:delete_value(?SHADER_PRW_VBO),
276            wings_vbo:delete(Vbo),
277            ignore
278    end.
279
280%% function required to ensure a config file with image on shader can be correctly
281%% load by file name or by name (image in Wings3D), otherwise we use the default
282%% auvBG in order to avoid GLSL issues by not receiving an image.
283%% The Filename parameter comes from .auv config file
284load_image(ImgName, Filename) when is_list(Filename) ->
285    Is = wings_image:images(),
286    ImgsId = [{Name,TexId} || {TexId, #e3d_image{name=Name}} <- Is],
287    case filelib:is_file(Filename) of
288        true ->
289            %% lets check if the file was not loaded before (using the same var name)
290            case lists:keyfind(ImgName,1,ImgsId) of
291                {_,Value} -> Value;
292                _ ->
293                    %% we try to load the file set in the .auv configuration file
294                    case wpa:image_read([{filename,Filename},
295                                         {alignment,1}]) of
296                        #e3d_image{}=Image ->
297                            {ImgName,wings_image:new_temp(ImgName, Image)};
298                        _ -> image_bg(ImgsId)
299                    end
300                end;
301        _ ->
302            %% Filename is an image name at Wings3D
303            case lists:keyfind(Filename,1,ImgsId) of
304                {_,Value} -> Value;
305                _ ->
306                    %% check for the image name already load
307                    case lists:keyfind(ImgName,1,ImgsId) of
308                        {_,Value} -> Value;
309                        _ -> image_bg(ImgsId)
310                    end
311            end
312    end;
313%% after the first call, in the further ones the Filename will be
314%% the image info {name,id}
315load_image(_, Filename) -> Filename.
316
317image_bg(ImgsId) ->
318    AuvBG =
319        case lists:keyfind("auvBG",1,ImgsId) of
320            {_,Value} -> Value;
321            _ -> wings_image:new("auvBG",wpc_autouv:bg_image())
322        end,
323    {"auvBG",AuvBG}.
324
325options(auv_background, [auv_background, {type_sel,Type},{Image,_},Color],_,_) ->
326    Enable = fun(_, What, Fields) ->
327		     IsImage = image == What,
328		     wings_dialog:enable(image_sel, IsImage, Fields),
329		     wings_dialog:enable(col_sel,  not IsImage, Fields)
330	     end,
331    [{hradio,[{?__(1,"Image"),image},{?__(2,"Color"),color}],
332      Type,[{key,type_sel},{hook, Enable}]},
333     {hframe,[{label,?__(1,"Image")},image_selector(Image,[])],
334      [{key, image_sel}]},
335     {hframe,[{label,?__(2,"Color")},{color,fix(Color,wings_gl:have_fbo())}],
336      [{key, col_sel}]}];
337options(auv_background, _Bad,Sh,SphereData) ->
338    options(auv_background, ?OPT_BG,Sh,SphereData);
339
340options(auv_edges,[auv_edges, Type,Color,Size,UseVtxColors],_,_) ->
341    [{vradio,[{?__(3,"Draw All Edges"),all_edges},
342	      {?__(4,"Draw Border Edges"), border_edges}],
343      Type, []},
344     {hframe,[{label,?__(5,"Edge Color:")},{color,Color}]},
345     {hframe,[{label,?__(6,"Edge Width:")},{text,Size,[{range,{0.0,100.0}}]}]},
346     {?__(8,"Use vertex colors (on border edges)"),UseVtxColors}
347    ];
348options(auv_edges,_,Sh,SphereData) ->
349    options(auv_edges,?OPT_EDGES,Sh,SphereData);
350
351options({shader,Id}=Opt,Vals,Sh,SphereData) ->
352    {value,Shader} = lists:keysearch(Id,#sh.id,Sh),
353    options_1(Opt,Vals,Sh,SphereData,Shader);
354
355options(Command,Vals,_,_) ->
356    io:format("~p: ~p~n",[Command, Vals]),
357    exit(unknown_default).
358
359options_1(Opt, Vals0, _, _, #sh{preview=false}=Shader) ->
360    FrmShader =
361        case Vals0 of
362            [Opt|Vals] ->
363                shader_options(Shader,[],Vals);
364            _ ->
365                shader_options(Shader,[],[])
366        end,
367    [{vframe, FrmShader}];
368
369options_1(Opt, Vals0, Sh, {Ts,SphereMesh}, Shader) ->
370    Preview =
371        fun(GLCanvas, _Fields) ->
372            wings_light:init_opengl(),
373            %% we create the sphere data for preview once
374            case wings_pref:get_value(?SHADER_PRW_VBO) of
375                undefined ->
376                    Vbo = setup_sphere_vbo(SphereMesh),
377                    wings_pref:set_value(?SHADER_PRW_VBO,Vbo);
378                Vbo0 -> Vbo = Vbo0
379            end,
380            tex_preview(GLCanvas,Vbo)
381        end,
382
383    Refresh =
384        fun(Key, Value, Fields) ->
385            GLCanvas = wings_dialog:get_widget(preview, Fields),
386            case wxWindow:isShown(GLCanvas) of
387                false -> ok;
388                true ->
389                    %% update the Vals list with the content of Fields plus
390                    %% the current field changed (Key) which is not updated
391                    %% in Fields
392                    Vals = update_values(Key,Value,Vals0,Fields),
393                    %% creating the texture preview image
394                    wings_gl:setCurrent(GLCanvas, ?GET(gl_context)),
395                    PrwImg = get_texture_preview({Opt,Vals},Sh,Ts),
396                    %% updating the image
397                    prw_img_id(PrwImg),
398                    case os:type() of
399                        {_, darwin} -> %% workaround wxWidgets 3.0.4 and mojave
400                            Preview(GLCanvas, Fields),
401                            wxGLCanvas:swapBuffers(GLCanvas);
402                        _ ->
403                            wxWindow:refresh(GLCanvas)
404                    end
405            end
406        end,
407
408    FrmShader =
409        case Vals0 of
410            [Opt|Vals] ->
411                shader_options(Shader,[{hook,Refresh}],Vals);
412            _ ->
413                shader_options(Shader,[{hook,Refresh}],[])
414        end,
415    [{hframe, [
416        {vframe, [{custom_gl,?PREVIEW_SIZE,?PREVIEW_SIZE,Preview,
417                   [{key, preview}, {proportion, 1}, {flag, ?wxEXPAND bor ?wxALL}]}],
418                 [{title, "Preview"}]},
419        {vframe, FrmShader, [{title, "Parameters"}]}]
420    }].
421
422shader_options(#sh{args=Args,def=Defs,file=File}, OptDef, Vals) ->
423    case shader_menu(Args,OptDef,reverse(Vals),[]) of
424	{failed,_} ->
425	    case shader_menu(Args,OptDef,Defs,[]) of
426		{failed,What} ->
427		    io:format("AUV: Bad default value ~p in ~p~n",
428			      [What, File]),
429		    [];
430		Menues -> Menues
431	    end;
432	Menues ->
433	    Menues
434    end.
435shader_menu([{uniform,color,_,_,Label}|As],OptDef,[Col={_,_,_,_}|Vs],Acc) ->
436    Menu = {hframe, [{label,Label},{color,Col,OptDef}]},
437    shader_menu(As,OptDef,Vs,[Menu|Acc]);
438shader_menu([{uniform,float,_,_,Label}|As],OptDef,[Def|Vs],Acc)
439  when is_number(Def) ->
440    Menu = {hframe,[{label,Label},
441		    {text,Def,[{range,{'-infinity',infinity}}]++OptDef}]},
442    shader_menu(As,OptDef,Vs,[Menu|Acc]);
443shader_menu([{uniform,bool,_,_,Label}|As],OptDef,[Def|Vs],Acc)
444  when is_boolean(Def) ->
445    Menu = {Label, Def, OptDef},
446    shader_menu(As,OptDef,Vs,[Menu|Acc]);
447shader_menu([{uniform,{slider,From,To},_,_,Label}|As],OptDef,[Def|Vs],Acc)
448  when is_number(Def) ->
449    Menu = {hframe,[{label,Label},
450		    {slider,{text,Def,[{range,{From,To}},{width,5}]++OptDef}}]},
451    shader_menu(As,OptDef,Vs,[Menu|Acc]);
452shader_menu([{uniform,{image,_},_,_Def,Label}|As],OptDef,[Def|Vs],Acc) ->
453    Image = case Def of {Im,_} -> Im; _ -> Def end,
454    Menu = {hframe,[{label,Label},image_selector(Image,OptDef)]},
455    shader_menu(As,OptDef,Vs,[Menu|Acc]);
456shader_menu([{uniform,menu,_,_,Labels}|As],OptDef,[Def|Vs],Acc) ->
457    Menu = {menu,Labels,Def,[]++OptDef},
458    shader_menu(As,OptDef,Vs,[Menu|Acc]);
459shader_menu([{auv,{auv_send_texture,Label,Def0}}|As],OptDef,Vs0,Acc) ->
460    case Vs0 of
461	[{auv_send_texture, Def}|Vs] -> ok;
462	[Def|Vs] -> ok;
463	[] -> Def = Def0, Vs = []
464    end,
465    Menu = {Label, Def, [{key,auv_send_texture}]},
466    shader_menu(As,OptDef,Vs,[Menu|Acc]);
467shader_menu([{auv,_Skip}|As],OptDef,Vs,Acc) ->
468    shader_menu(As,OptDef,Vs,Acc);
469shader_menu([What|_],_,[Def|_],_) ->
470    {failed,{What,value,Def}};
471shader_menu([What|_],_,[],_) ->
472    {failed,What};
473shader_menu([],_,_,Acc) ->
474    Acc.
475
476image_selector(Default,OptDef) ->
477    Is = wings_image:images(),
478    Menu = [{Name,{Name,TexId}} || {TexId, #e3d_image{name=Name}} <- Is],
479    case lists:keysearch(Default,1,Menu) of
480	{value,{_,What}} -> Def = What;
481	_ -> case Menu of
482		 [{_,Def}|_] -> Def;
483		 _ -> Def = void
484	     end
485    end,
486    {menu,Menu,Def,OptDef}.
487
488enable_opt() ->
489    {hook,
490     fun({_W, Id}, Pass, Fields) ->
491	     Disable = Pass =:= ignore orelse Pass =:= auv_faces,
492	     wings_dialog:enable({opt_butt, Id},  not Disable, Fields),
493	     case wings_dialog:get_value({auv_opt, Id}, Fields) of
494		 [Pass|_] ->
495		     ok;
496		 _ -> %% Changed type reset options
497		     wings_dialog:set_value({auv_opt, Id}, [], Fields)
498	     end
499     end}.
500
501option_hook(Id,Renderers,Shaders) ->
502    {hook,
503     fun(_Key, button_pressed, Fields) ->
504	     Env = wx:get_env(),
505	     spawn(fun() ->
506			   %% Need open dialog in dialog from another process
507			   wx:set_env(Env),
508			   option_dialog(Id, Fields, Renderers, Shaders),
509                           wings_wm:psend(send_once, dialog_blanket, preview)
510		   end)
511     end
512    }.
513
514renderer(Id,[Renderer={_,Id}|_R]) ->  Renderer;
515renderer(Id,[_|R]) ->  renderer(Id,R).
516
517%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
518%% Texture preview handling
519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
520
521update_values(Key, Val, Vals, Fields) ->
522    update_values(1,Key,Val,Vals,Fields,[]).
523
524update_values(_, _, _, [], _, Acc) -> lists:reverse(Acc);
525update_values(Key, Key, Value, [_|Vals], Fields, Acc) ->
526    update_values(Key+1,Key,Value,Vals,Fields,[Value|Acc]);
527update_values(Key, CKey, CValue, [H|Vals], Fields, Acc) ->
528    try wings_dialog:get_value(Key,Fields) of
529        Value ->
530            update_values(Key+1,CKey,CValue,Vals,Fields,[Value|Acc])
531    catch
532        _:_ ->
533            update_values(Key+1,CKey,CValue,Vals,Fields,[H|Acc])
534    end.
535
536get_texture_preview(Render,Shaders,Ts) ->
537    Options = #opt{texsz = {?SHADER_PRW_SIZE,?SHADER_PRW_SIZE},no_renderers=2,renderers=[Render]},
538    Compiled = compile_shaders([Render],Shaders),
539    Passes = get_passes([Render],{Shaders,Compiled}),
540    Reqs = get_requirements(Shaders),
541    Res  = render_image_preview(Ts, Passes, Options, Reqs),
542    delete_shaders(Compiled),
543    Res.
544
545render_image_preview(Geom0, Passes, #opt{texsz={TexW,TexH}}, Reqs) ->
546    gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
547    UsingFbo = setup_fbo(TexW,TexH),
548    {W0,H0} = if not UsingFbo ->
549                  wings_wm:top_size();
550              true ->
551                  gl:blendEquationSeparate(?GL_FUNC_ADD, ?GL_MAX),
552                  {TexW,TexH}
553              end,
554    {W,Wd} = calc_texsize(W0, TexW),
555    {H,Hd} = calc_texsize(H0, TexH),
556    set_viewport({0,0,W,H}, 1),
557    Geom = make_vbo(Geom0, Reqs),
558    try
559        Do = fun(Pass) ->
560                if not UsingFbo ->
561                    Pass(Geom,undefined);
562                true ->
563                    fill_bg_tex(UsingFbo),
564                    Pass(Geom,UsingFbo)
565                end
566             end,
567        Dl = fun() -> foreach(Do, Passes) end,
568        ImageBins = get_texture(0, Wd, 0, Hd, {W,H}, Dl, UsingFbo,[]),
569        ImageBin = merge_texture(ImageBins, Wd, Hd, W*3, H, []),
570        if not UsingFbo ->
571            #e3d_image{image=ImageBin,width=TexW,height=TexH};
572        true ->
573            #e3d_image{image=ImageBin,width=TexW,height=TexH,
574                       type=r8g8b8a8,bytes_pp=4}
575        end
576    catch _:What:Where ->
577        exit({What,Where})
578    after
579        case UsingFbo of
580            false -> ignore;
581            #sh_conf{fbo_d=DeleteMe} -> DeleteMe()
582        end,
583        wings_vbo:delete(Geom#ts.vbo),
584        gl:readBuffer(?GL_BACK),
585        gl:popAttrib(),
586        gl:blendEquationSeparate(?GL_FUNC_ADD, ?GL_FUNC_ADD),
587        gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT),
588        ?ERROR
589    end.
590
591create_sphere_data() ->
592    {We, SphereData} = setup_sphere_mesh(),
593    Shs = gb_trees:enter(We#we.id, We, gb_trees:empty()),
594    Maps =
595        case wings_pref:get_value(?SHADER_PRW_NAME) of
596            undefined -> [];
597            {TxId,_} -> {maps,[{diffuse,TxId}]}
598        end,
599    Mat = [{opengl,[{diffuse, {1.0,1.0,1.0,1.0}},
600                    {emission,{0.0,0.0,0.0,1.0}},
601                    {metallic, 0.0},
602                    {roughness, 1.0},
603                    {vertex_colors,ignore}]}] ++ Maps,
604    Mtab = gb_trees:enter(list_to_atom(?SHADER_PRW_NAME),Mat,gb_trees:empty()),
605    FakeSt0 = #st{sel=[],shapes=Shs,mat=Mtab},
606    Uvs = #uvstate{st=wpa:sel_set(face, [], FakeSt0),
607                   id      = We#we.id,
608                   mode    = object,
609                   matname = none},
610    FakeSt = FakeSt0#st{selmode=body,sel=[],shapes=gb_trees:empty(),bb=Uvs,
611                        repeatable=ignore,ask_args=none,drag_args=none},
612    Ts = setup(rebuild_charts(We, FakeSt),[normal]),
613    {Ts, SphereData}.
614
615prw_img_id(new) ->
616    case wings_pref:get_value(?SHADER_PRW_NAME) of
617        undefined ->
618            Image = bg_image_prw(),
619            Id = wings_image:new_hidden(?SHADER_PRW_NAME,Image),
620            TxId = wings_image:txid(Id),
621            wings_pref:set_value(?SHADER_PRW_NAME,{TxId,Id}),
622            TxId;
623        {TxId,_} ->
624            TxId
625    end;
626prw_img_id(#e3d_image{}=Image) ->
627    TxId =
628        case wings_pref:get_value(?SHADER_PRW_NAME) of
629            undefined ->
630                prw_img_id(new);
631            {TxId0,Id} ->
632                wings_pref:set_value(?SHADER_PRW_NAME,{TxId0,Id}),
633                TxId0
634        end,
635    init_texture(Image, TxId);
636prw_img_id(delete) ->
637    case wings_pref:get_value(?SHADER_PRW_NAME) of
638        undefined ->
639            ignore;
640        {_,Id} ->
641            wings_image:delete(Id),
642            wings_pref:delete_value(?SHADER_PRW_NAME)
643    end.
644
645bg_image_prw() ->
646    White = <<255,255,255,255>>,
647    Width = Height = ?SHADER_PRW_SIZE,
648    Row = fill_bg(Width,White),
649    Pixels = fill_bg(Height,Row),
650    #e3d_image{width=Width,height=Height,image=Pixels,
651               order=lower_left,bytes_pp=4,type=r8g8b8a8,name=?SHADER_PRW_NAME}.
652
653fill_bg(Width, White) ->
654    fill_bg(Width,White,<<>>).
655
656fill_bg(0, _, Acc) -> Acc;
657fill_bg(Width, White, Acc) -> fill_bg(Width-1,White,<<White/binary,Acc/binary>>).
658
659init_texture(#e3d_image{width=W,height=H,image=Bits,extra=Opts}, TxId) ->
660    gl:bindTexture(?GL_TEXTURE_2D, TxId),
661    FT = {Format,Type} = {?GL_RGBA, ?GL_UNSIGNED_BYTE},
662    Ft = case wings_pref:get_value(filter_texture, false) of
663             true -> linear;
664             false -> nearest
665         end,
666    {MinFilter,MagFilter} = proplists:get_value(filter, Opts, {mipmap, Ft}),
667    MMs = proplists:get_value(mipmaps, Opts, []),
668    gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_WRAP_S, ?GL_REPEAT),
669    gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_WRAP_T, ?GL_REPEAT),
670    case MinFilter of
671        mipmap ->
672            case lists:reverse(lists:keysort(4, MMs)) of
673                [{_,_,_,Max}|_] ->
674                    gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAX_LEVEL, Max-1);
675                [] ->
676                    gl:texParameteri(?GL_TEXTURE_2D, ?GL_GENERATE_MIPMAP, ?GL_TRUE)
677            end,
678            gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, ?GL_LINEAR_MIPMAP_LINEAR),
679            gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, filter(MagFilter));
680        _ ->
681            gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, filter(MagFilter)),
682            gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, filter(MinFilter))
683    end,
684    IntFormat = internal_format(FT),
685    gl:texImage2D(?GL_TEXTURE_2D, 0, IntFormat, W, H, 0, Format, Type, Bits),
686    [gl:texImage2D(?GL_TEXTURE_2D, Levl, IntFormat, MMW, MMH, 0, Format, Type, MM)
687     || {MM, MMW, MMH, Levl} <- MMs],
688    gl:bindTexture(?GL_TEXTURE_2D, 0),
689    ?CHECK_ERROR(),
690    TxId.
691
692filter(mipmap) -> ?GL_LINEAR_MIPMAP_LINEAR;
693filter(linear) -> ?GL_LINEAR;
694filter(nearest) -> ?GL_NEAREST.
695
696%% internal_format({?GL_BGR,?GL_UNSIGNED_BYTE}) -> ?GL_RGB;
697%% internal_format({?GL_BGRA,?GL_UNSIGNED_BYTE}) -> ?GL_RGBA;
698%% internal_format({?GL_RGBA,?GL_FLOAT}) -> ?GL_RGBA32F;
699%% internal_format({?GL_RGB,?GL_FLOAT}) -> ?GL_RGB32F;
700internal_format({Format,?GL_UNSIGNED_BYTE}) -> Format.
701
702rebuild_charts(We, St = #st{bb=UVS=#uvstate{st=Old,mode=Mode}}) ->
703    {Faces,FvUvMap} = auv_segment:fv_to_uv_map(Mode,We),
704    {Charts1,Cuts} = auv_segment:uv_to_charts(Faces, FvUvMap, We),
705    Charts2 = auv_segment:cut_model(Charts1, Cuts, We),
706    Charts = update_uv_tab(Charts2, FvUvMap),
707    St#st{sel=[],bb=UVS#uvstate{mode=update_mode(Faces,We),st=Old#st{sel=[]}},shapes=Charts}.
708
709update_mode(Faces0, #we{fs=Ftab}) ->
710    Fs = gb_sets:from_list(Faces0),
711    case gb_sets:size(Fs) == gb_trees:size(Ftab) of
712        true -> object;
713        false -> Fs
714    end.
715
716update_uv_tab(Cs, FvUvMap) ->
717    update_uv_tab_1(Cs, FvUvMap, []).
718
719update_uv_tab_1([#we{id=Id,name=#ch{vmap=Vmap}}=We0|Cs], FvUvMap, Acc) ->
720    Fs = wings_we:visible(We0),
721    UVs0 = wings_face:fold_faces(
722        fun(F, V, _, _, A) ->
723            OrigV = auv_segment:map_vertex(V, Vmap),
724            [{V,[F|OrigV]}|A]
725        end, [], Fs, We0),
726    case update_uv_tab_2(sort(UVs0), FvUvMap, 0.0, []) of
727        error ->
728            %% No UV coordinate for at least some vertices (probably
729            %% all) in the chart. Throw away this chart.
730            update_uv_tab_1(Cs, FvUvMap, Acc);
731        UVs1 ->
732            UVs = array:from_orddict(UVs1),
733            We = We0#we{vp=UVs},
734            update_uv_tab_1(Cs, FvUvMap, [{Id,We}|Acc])
735    end;
736update_uv_tab_1([], _, Acc) ->
737    gb_trees:from_orddict(sort(Acc)).
738
739update_uv_tab_2([{V,_}|T], FvUvMap, Z, [{V,_}|_]=Acc) ->
740    update_uv_tab_2(T, FvUvMap, Z, Acc);
741update_uv_tab_2([{V,Key}|T], FvUvMap, Z, Acc) ->
742    case gb_trees:get(Key, FvUvMap) of
743        {X,Y} ->
744            Pos = {X,Y,Z},
745            update_uv_tab_2(T, FvUvMap, Z, [{V,Pos}|Acc]);
746        _ ->
747            %% No UV-coordinate for this vertex. Abandon the entire chart.
748            error
749    end;
750update_uv_tab_2([], _, _, Acc) ->
751    lists:reverse(Acc).
752
753setup_sphere_mesh() ->
754    #{size:=Len, tris:= Tris, ns:=Normals, uvs:=UVs, tgs:=Tgs} =
755        wings_shapes:tri_sphere(#{subd=>4, ccw=>false, normals=>true, tgs=>true,
756                                  uvs=>true, scale=>0.45}),
757    Idx = length(Tris) div 3,
758    Fs = [#e3d_face{vs=[I*3,I*3+1,I*3+2],
759                    tx=[I*3,I*3+1,I*3+2],
760                    ns=[I*3,I*3+1,I*3+2]} || I <- lists:seq(0,Idx-1)],
761    Mesh = #e3d_mesh{vs=Tris,tx=UVs,ns=Normals,fs=Fs},
762    #we{vp=Vtab0} = We = wings_import:import_mesh(material,Mesh),
763    %% rotating the sphere slight above and right to better visualization
764    %% of the blending area of a triplanar shader
765    M0 = e3d_mat:rotate(30.0,{0.0,1.0,0.0}),
766    M1 = e3d_mat:rotate(-30.0,{1.0,0.0,0.0}),
767    M = e3d_mat:mul(M0,M1),
768    Vtab =
769        array:sparse_foldl(fun(V, Value0, Acc)->
770                              Value = e3d_mat:mul_point(M,Value0),
771                              array:set(V,Value,Acc)
772                           end,Vtab0,Vtab0),
773    Data = {Len, zip(Tris, Normals, UVs, Tgs)},
774    {We#we{id=1,vp=Vtab,mat=list_to_atom(?SHADER_PRW_NAME)},Data}.
775
776setup_sphere_vbo({Len,Data}) ->
777    Layout = [vertex, normal, uv, tangent],
778    D = fun(#{preview := PreviewMat} = RS0) ->
779        RS1 = wings_shaders:use_prog(1, RS0),
780        RS2 = lists:foldl(fun apply_material/2, RS1, PreviewMat),
781        gl:drawArrays(?GL_TRIANGLES, 0, Len*3),
782        wings_shaders:use_prog(0, RS2)
783        end,
784    wings_vbo:new(D, Data, Layout).
785
786apply_material({{tex, Type}=TexType, TexId}, Rs0) ->
787    case wings_shaders:set_state(TexType, TexId, Rs0) of
788        {false, Rs0} ->
789            Rs0;
790        {true, Rs1} when TexId =:= none ->
791            wings_shaders:set_uloc(texture_var(Type), enable(false), Rs1);
792        {true, Rs1} ->
793            gl:activeTexture(?GL_TEXTURE0 + tex_unit(Type)),
794            gl:bindTexture(?GL_TEXTURE_2D, TexId),
795            gl:activeTexture(?GL_TEXTURE0),
796            wings_shaders:set_uloc(texture_var(Type), enable(true), Rs1)
797    end;
798apply_material({Type, Value}, Rs0)
799    when Type =:= diffuse; Type =:= emission; Type =:= metallic; Type =:= roughness ->
800    wings_shaders:set_uloc(Type, Value, Rs0);
801apply_material({_Type,_}, Rs0) ->
802    io:format("~p:~p: unsupported type ~p~n",[?MODULE,?LINE,_Type]),
803    Rs0.
804
805enable(true)  -> 1;
806enable(false) -> 0.
807
808texture_var(diffuse) -> 'UseDiffuseMap';
809texture_var(normal) ->  'UseNormalMap';
810texture_var(pbr_orm) -> 'UsePBRMap'; %% red = occlusion green = roughness blue = metallic
811texture_var(emission) -> 'UseEmissionMap'.
812
813tex_unit(diffuse) -> ?DIFFUSE_MAP_UNIT;
814tex_unit(normal) -> ?NORMAL_MAP_UNIT;
815tex_unit(pbr_orm) -> ?PBR_MAP_UNIT; %% red = occlusion green = roughness blue = metallic
816tex_unit(emission) -> ?EMISSION_MAP_UNIT.
817
818tex_preview(Canvas, Vbo) ->
819    TxId = prw_img_id(new),
820    Maps = [{{tex,diffuse}, TxId}],
821    {W,H} = wxWindow:getSize(Canvas),
822    Scale = wxWindow:getContentScaleFactor(Canvas),
823
824    gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
825    gl:viewport(0, 0, round(W*Scale), round(H*Scale)),
826    {BR,BG,BB, _} = wxWindow:getBackgroundColour(wxWindow:getParent(Canvas)),
827    BGC = fun(Col) -> (Col-15) / 255 end,
828    gl:clearColor(BGC(BR),BGC(BG),BGC(BB),1.0),
829    gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT),
830    gl:matrixMode(?GL_PROJECTION),
831    gl:loadIdentity(),
832    Fov = 45.0, Aspect = W/H,
833    MatP = e3d_transform:perspective(Fov, Aspect, 0.01, 256.0),
834    gl:multMatrixd(e3d_transform:matrix(MatP)),
835    gl:matrixMode(?GL_MODELVIEW),
836    gl:loadIdentity(),
837    Dist = (0.5/min(1.0,Aspect)) / math:tan(Fov/2*math:pi()/180),
838    Eye = {0.0,0.0,Dist}, Up = {0.0,1.0,0.0},
839    MatMV = e3d_transform:lookat(Eye, {0.0,0.0,0.0}, Up),
840    gl:multMatrixd(e3d_transform:matrix(MatMV)),
841    gl:shadeModel(?GL_SMOOTH),
842    Diff  = {diffuse, {1.0,1.0,1.0,1.0}},
843    Emis  = {emission, {0.0,0.0,0.0,1.0}},
844    Metal = {metallic, 0.0},
845    Rough = {roughness, 1.0},
846    Material = [Diff, Emis, Metal, Rough | Maps],
847    gl:enable(?GL_BLEND),
848    gl:enable(?GL_DEPTH_TEST),
849    gl:enable(?GL_CULL_FACE),
850    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
851    gl:color4ub(255, 255, 255, 255),
852    RS = #{ws_eyepoint=>Eye, view_from_world=>MatMV, preview=>Material},
853    wings_dl:call(Vbo, RS),
854    gl:disable(?GL_BLEND),
855    gl:shadeModel(?GL_FLAT),
856    gl:popAttrib(),
857    case os:type() of
858        {_, darwin} ->
859            %% Known problem during redraws before window is shown
860            %% only reset error check
861            _ = gl:getError();
862        _ ->
863            wings_develop:gl_error_check("Rendering texture viewer")
864    end.
865
866zip(Vs, Ns, UVs, Tgs) ->
867    zip_0(Vs, Ns, UVs, Tgs, []).
868
869zip_0([V|Vs], [N|Ns], [UV|UVs], [T|Ts], Acc) ->
870    zip_0(Vs, Ns, UVs,Ts, [V,N,UV,T|Acc]);
871zip_0([], [], [], [],Acc) -> Acc.
872
873%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
874%% Texture creation
875%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
876
877get_texture(St = #st{bb=#uvstate{}}, {Options,Shaders}) ->
878    Compiled = compile_shaders(Options#opt.renderers,Shaders),
879    Passes = get_passes(Options#opt.renderers,{Shaders,Compiled}),
880    Reqs = get_requirements(Shaders),
881    Ts   = setup(St,Reqs),
882    Res  = render_image(Ts, Passes, Options, Reqs),
883    delete_shaders(Compiled),
884    Res.
885
886%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
887%% Texture Rendering
888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
889
890render_image(Geom0, Passes, #opt{texsz={TexW,TexH}}, Reqs) ->
891    gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
892
893    Current = wings_wm:viewport(),
894    Scale = wings_wm:win_scale(),
895    UsingFbo = setup_fbo(TexW,TexH),
896    {W0,H0} = if not UsingFbo ->
897                      wings_wm:top_size();
898		 true ->
899                      gl:blendEquationSeparate(?GL_FUNC_ADD, ?GL_MAX),
900                      {TexW,TexH}
901	      end,
902    {W,Wd} = calc_texsize(W0, TexW),
903    {H,Hd} = calc_texsize(H0, TexH),
904%%    io:format("Get texture sz ~p ~p ~n", [{W,Wd},{H,Hd}]),
905    set_viewport({0,0,W,H}, 1),
906    Geom = make_vbo(Geom0, Reqs),
907    try
908        Do = fun(Pass) ->
909                     if not UsingFbo ->
910                             Pass(Geom,undefined);
911                        true ->
912                             fill_bg_tex(UsingFbo),
913                             Pass(Geom,UsingFbo)
914                     end
915             end,
916        Dl = fun() -> foreach(Do, Passes) end,
917	ImageBins = get_texture(0, Wd, 0, Hd, {W,H}, Dl, UsingFbo,[]),
918	ImageBin = merge_texture(ImageBins, Wd, Hd, W*3, H, []),
919	if not UsingFbo ->
920		#e3d_image{image=ImageBin,width=TexW,height=TexH};
921	   true ->
922		#e3d_image{image=ImageBin,width=TexW,height=TexH,
923			   type=r8g8b8a8,bytes_pp=4}
924	end
925    catch _:What:Where ->
926	    exit({What,Where})
927    after
928	case UsingFbo of
929            false -> ignore;
930            #sh_conf{fbo_d=DeleteMe} -> DeleteMe()
931        end,
932        wings_vbo:delete(Geom#ts.vbo),
933        set_viewport(Current, Scale),
934        gl:readBuffer(?GL_BACK),
935        gl:popAttrib(),
936        gl:blendEquationSeparate(?GL_FUNC_ADD, ?GL_FUNC_ADD),
937        gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT),
938        ?ERROR
939    end.
940
941make_vbo(#ts{uv=UV}=Ts, Reqs) ->
942    {Layout, Bin} = make_bin_normal(Ts, Reqs, [{vertex2d, 0, 0}], UV),
943    Ts#ts{vbo=wings_vbo:new(dynamic, Bin, {predefined, Layout})}.
944
945make_bin_normal(#ts{n=Ns}=Ts, Reqs, Layout, Bin) ->
946    case lists:member(normal, Reqs) of
947	true  -> make_bin_color(Ts, Reqs, [{normal, 0, byte_size(Bin)}|Layout],
948                                <<Bin/binary, Ns/binary>>);
949	false -> make_bin_color(Ts, Reqs, Layout, Bin)
950    end.
951
952make_bin_color(#ts{vc=Vc}=Ts, Reqs, Layout, Bin) ->
953    make_bin_pos3d(Ts, Reqs, [{color, 0, byte_size(Bin)}|Layout], <<Bin/binary, Vc/binary>>).
954
955make_bin_pos3d(#ts{pos=Pos}=Ts, Reqs, Layout, Bin) ->
956    case have_shaders() of
957        true  -> make_bin_binormal(Ts, Reqs, [{{tex,1,3}, 0, byte_size(Bin)}|Layout],
958                                   <<Bin/binary, Pos/binary>>);
959	false -> make_bin_binormal(Ts, Reqs, Layout, Bin)
960    end.
961
962make_bin_binormal(#ts{bi=BiNs}, Reqs, Layout, Bin) ->
963    case lists:member(binormal, Reqs) of
964	true -> {lists:reverse([{{tex,2,4}, 0, byte_size(Bin)}|Layout]),
965                 <<Bin/binary, BiNs/binary>>};
966	false -> {lists:reverse(Layout), Bin}
967    end.
968
969%%%%%%%%% FBO stuff
970
971setup_fbo(W,H) ->
972    case wings_gl:setup_fbo({W,H}, [{color,[]}, {color,[]}, {depth,[]}]) of
973	not_supported -> false;
974	List ->
975	    [Col1,Col2] = [Col || {color, Col} <- List],
976	    #sh_conf{texsz={W,H},fbo_r=Col2,fbo_w=Col1,
977		     fbo_d=fun() -> wings_gl:delete_fbo(List) end}
978    end.
979
980error_msg(Line) ->
981    case wings_gl:error_string(gl:getError()) of
982	no_error -> ok;
983	Err -> io:format("~p:~p: ~p ~n",[?MODULE, Line,Err]), error
984    end.
985
986draw_texture_square() ->
987    VertexUvQ = << 0.0:?F32,0.0:?F32, 0.0:?F32,0.0:?F32,
988                   1.0:?F32,0.0:?F32, 1.0:?F32,0.0:?F32,
989                   1.0:?F32,1.0:?F32, 1.0:?F32,1.0:?F32,
990                   0.0:?F32,1.0:?F32, 0.0:?F32,1.0:?F32>>,
991    wings_vbo:draw(fun(_) -> gl:drawArrays(?GL_QUADS, 0, 4) end, VertexUvQ, [vertex2d, uv]).
992
993fill_bg_tex(#sh_conf{fbo_w=Prev}) ->
994    gl:drawBuffer(?GL_COLOR_ATTACHMENT1_EXT),
995    gl:bindTexture(?GL_TEXTURE_2D, Prev),
996    gl:texEnvi(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, ?GL_REPLACE),
997    gl:enable(?GL_TEXTURE_2D),
998    gl:disable(?GL_BLEND),
999    draw_texture_square(),
1000    gl:disable(?GL_TEXTURE_2D),
1001    gl:drawBuffer(?GL_COLOR_ATTACHMENT0_EXT),
1002    ok.
1003
1004get_texture(Wc, Wd, Hc, Hd, {W,H}=Info, DL, UsingFbo, ImageAcc)
1005  when Wc < Wd, Hc < Hd ->
1006    gl:pixelStorei(?GL_UNPACK_ALIGNMENT, 1),
1007    gl:clearColor(1.0, 1.0, 1.0, 1.0),
1008    gl:shadeModel(?GL_SMOOTH),
1009    gl:disable(?GL_CULL_FACE),
1010    gl:disable(?GL_LIGHTING),
1011    texture_view(Wc, Wd, Hc, Hd),
1012    DL(),
1013    gl:flush(),
1014    {Sz,Type} =
1015	case UsingFbo of
1016	    false ->
1017		gl:readBuffer(?GL_BACK),
1018		{3,?GL_RGB};
1019	    _ ->
1020		gl:readBuffer(?GL_COLOR_ATTACHMENT0_EXT),
1021		{4,?GL_RGBA}
1022	end,
1023    Mem = wings_io:get_buffer(W*H*Sz, ?GL_UNSIGNED_BYTE),
1024    gl:readPixels(0,0,W,H,Type,?GL_UNSIGNED_BYTE,Mem),
1025    ImageBin = wings_io:get_bin(Mem),
1026    get_texture(Wc+1, Wd, Hc, Hd, Info, DL, UsingFbo, [ImageBin|ImageAcc]);
1027get_texture(_Wc,Wd,Hc,Hd, Info, Dl, UsingFbo, ImageAcc) when Hc < Hd ->
1028    get_texture(0, Wd, Hc+1, Hd, Info, Dl, UsingFbo, ImageAcc);
1029get_texture(_,_,_,_,_,_,_,ImageAcc) -> reverse(ImageAcc).
1030
1031texture_view(WC, WD, HC, HD) ->
1032    gl:matrixMode(?GL_PROJECTION),
1033    gl:loadIdentity(),
1034    Ortho = e3d_transform:ortho(WC/WD, (1+WC)/WD, HC/HD, (1+HC)/HD, -1.0, 1.0),
1035    gl:loadMatrixd(e3d_transform:matrix(Ortho)),
1036
1037    gl:matrixMode(?GL_MODELVIEW),
1038    gl:loadIdentity(),
1039    gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_FILL).
1040
1041merge_texture_cols(List, Wd, Wd, _W, _RowC, Acc) ->
1042    {list_to_binary(reverse(Acc)), List};
1043merge_texture_cols([H|R], Wc, Wd, W, RowC, Acc) ->
1044    SkipBytes = RowC*W,
1045    <<_:SkipBytes/binary, Row:W/binary,_/binary>> = H,
1046    merge_texture_cols(R, Wc + 1, Wd, W, RowC, [Row|Acc]).
1047
1048merge_texture_rows(_ImageBins, H, H, _W, _Wd,Acc, Last) ->
1049    {list_to_binary(reverse(Acc)), Last};
1050merge_texture_rows(ImageBins, RowC, H, W, Wd, Acc, _) ->
1051    {Row, Rest} = merge_texture_cols(ImageBins, 0, Wd, W, RowC, []),
1052    merge_texture_rows(ImageBins, RowC + 1, H,W,Wd, [Row|Acc], Rest).
1053
1054merge_texture([Bin],1,1,_,_,[]) ->   Bin;  %% No merge needed.
1055merge_texture(Bins, 1,_,_,_,[]) ->   list_to_binary(Bins);  %% No merge needed.
1056merge_texture([],_,_,_,_,Acc) ->
1057    list_to_binary(reverse(Acc));
1058merge_texture(ImageBins,Wd,Hd,W,H,Acc) ->
1059    {Col, Bins} = merge_texture_rows(ImageBins, 0, H, W, Wd, [], ImageBins),
1060    merge_texture(Bins,Wd,Hd,W,H,[Col|Acc]).
1061
1062calc_texsize(Vp, Tex) ->
1063    calc_texsize(Vp, Tex, Tex).
1064
1065calc_texsize(Vp, Tex, Orig) when Tex =< Vp ->
1066    {Tex,Orig div Tex};
1067calc_texsize(Vp, Tex, Orig) ->
1068    calc_texsize(Vp, Tex div 2, Orig).
1069
1070get_pref(Key, Def) ->
1071    wpa:pref_get(autouv, Key, Def).
1072set_pref(KeyVals) ->
1073    wpa:pref_set(autouv, KeyVals).
1074
1075pref_to_list(#opt{texsz={TexSz,_TexSz}, no_renderers=NoR,
1076		  renderers=[{auv_background,Bg}|Rs]}) ->
1077    [{texsz, TexSz},{{auv_pass,0},auv_background},{{auv_opt,0},Bg}|
1078     r2list(Rs,1,NoR)].
1079
1080r2list([{Type, Opts}|Rest], Id, Max) when Id < Max ->
1081    [{{auv_pass,Id}, Type},{{auv_opt,Id}, Opts}|r2list(Rest,Id+1,Max)];
1082r2list([], Id, Max) when Id < Max ->
1083    [{{auv_pass,Id}, ignore},{{auv_opt,Id},[]}|r2list([],Id+1,Max)];
1084r2list([], _Id, _Max)  -> [].
1085
1086list_to_prefs([{texsz, TexSz}|Rest]) ->
1087    LR = listOfRenders(Rest,[]),
1088    #opt{texsz={TexSz,TexSz},no_renderers=length(LR),renderers=LR}.
1089
1090listOfRenders([{{auv_pass,_},ignore},_|Rest],Acc) ->
1091    listOfRenders(Rest,[{ignore,[]}|Acc]);
1092listOfRenders([{{auv_pass,_},Type},{{auv_opt,_},Opts}|Rest],Acc) ->
1093    listOfRenders(Rest,[{Type,Opts}|Acc]);
1094listOfRenders([],Acc) -> reverse(Acc).
1095
1096gen_tx_sizes(Sz, Acc) when Sz < 128 -> Acc;
1097gen_tx_sizes(Sz, Acc) ->
1098    Bytes = Sz*Sz*3,
1099    Mb = 1024*1024,
1100    SzStr = if
1101		Bytes < 1024*1024 ->
1102		    io_lib:format("(~pKb)",[Bytes div 1024]);
1103		true ->
1104		    io_lib:format("(~pMb)",[Bytes div Mb])
1105	    end,
1106    Str0 = io_lib:format("~px~p ", [Sz,Sz]),
1107    Str = lists:flatten([Str0|SzStr]),
1108    gen_tx_sizes(Sz div 2, [{Str,Sz}|Acc]).
1109
1110set_viewport({X,Y,W,H}=Viewport, Scale) ->
1111    put(wm_viewport, Viewport),
1112    gl:viewport(X, Y, round(W*Scale), round(H*Scale)).
1113
1114
1115%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1116%% Data setup
1117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1118
1119setup(#st{bb=#uvstate{id=RId,st=#st{shapes=Sh0}}}=St, Reqs) ->
1120    We = gb_trees:get(RId,Sh0),
1121    {Charts,{_Cnt,UVpos,Vpos,Ns,Ts,BB,Vc}} = setup_charts(St, We, Reqs),
1122    #ts{charts=Charts,
1123	uv = to_bin(UVpos,uv),
1124	pos= to_bin(Vpos,pos),
1125	n  = to_bin(Ns,pos),
1126	bi = build_tangents(Ts),
1127	bb = BB,
1128	vc = to_bin(Vc, vertex)}.
1129
1130setup_charts(#st{shapes=Cs0,selmode=Mode,sel=Sel}, We, Reqs) ->
1131    Ns = case member(normal, Reqs) orelse member(binormal, Reqs) of
1132	     true -> setup_normals(We#we{mirror=none});
1133	     false -> []
1134	 end,
1135    Z = e3d_vec:zero(),
1136    TSA = array:new([{default, {Z,Z}}]),
1137    Start = {0,[],[],[],{TSA,[]},[],[]}, %% {UvPos,3dPos,Normal,Tangent,Vc}
1138    Shapes = if Sel =:= [] ->
1139		     gb_trees:values(Cs0);
1140		Mode =:= body ->
1141		     [gb_trees:get(Id,Cs0) || {Id,_} <- Sel];
1142		true ->
1143		     gb_trees:values(Cs0)
1144	     end,
1145    Setup = fun(Ch,Acc) ->
1146		    setup_chart(Ch, Ns, Reqs, We, Acc)
1147	    end,
1148    lists:mapfoldl(Setup, Start, Shapes).
1149
1150setup_chart(#we{id=Id}=Ch, Ns, Reqs, WWe, State0) ->
1151    OEs0 = outer_verts(Ch),
1152    BB = [],
1153    {Fs,{OEs,UvBB,State}} = create_faces(Ch, WWe, Ns, Reqs, {OEs0,BB,State0}),
1154    {#chart{id=Id,fs=Fs,oes=OEs,bb_uv=UvBB},State}.
1155
1156create_faces(#we{vp=Vtab,name=#ch{vmap=Vmap}}=We,
1157	     #we{vp=Vt3d}=RealWe, NTab, Reqs, State) ->
1158    Fs = wings_we:visible(We),
1159    C=fun(Face,{OEs,UvBB,{Cnt,UVpos,Vpos,Ns,Ts0,PosBB,Vc}}) ->
1160	      Vs0 = wings_face:vertices_ccw(Face,We),
1161	      UVcoords = [array:get(V, Vtab) || V <- Vs0],
1162	      Coords   = [array:get(map_vertex(V,Vmap),Vt3d)
1163			  || V <- Vs0],
1164	      Normals = if
1165			    NTab=:= [] ->
1166				[];
1167			    true ->
1168				fix_normals(Vs0, Vmap,
1169					    wings_va:face_attr([vertex|uv],
1170							       Face, RealWe),
1171					    gb_trees:get(Face,NTab))
1172			end,
1173	      OldVc  = fix_vc(Vs0, Face, RealWe, Vmap),
1174	      Len = length(Vs0),
1175	      FaceVs = lists:seq(0, Len-1),
1176	      Vs = case Len of
1177		       3 -> FaceVs;
1178		       Len -> triangulate(FaceVs,UVcoords)
1179		   end,
1180%	      io:format("Face ~p Normals ~p~n", [Face,Normals]),
1181	      Ts = calc_tang_vecs(Vs,Vs0,Coords,UVcoords,Normals,Ts0,Reqs),
1182	      Indx = fun(I) -> [V+Cnt || V <- I] end,
1183	      {#fs{vs=Indx(Vs),vse=Indx(FaceVs),id=Face},
1184	       {map_oes(OEs, Vs0, Cnt+Len-1, Face),
1185		e3d_vec:bounding_box(UvBB ++ UVcoords),
1186		{Cnt+Len,
1187		 UVcoords ++ UVpos,
1188		 Coords   ++ Vpos,
1189		 Normals  ++ Ns,
1190		 Ts,
1191		 e3d_vec:bounding_box(PosBB ++ Coords),
1192		 OldVc   ++ Vc}}}
1193      end,
1194    lists:mapfoldl(C, State, Fs).
1195
1196triangulate(FaceVs,Vcoords) ->
1197    E3dface = #e3d_face{vs=FaceVs},
1198    T3dfaces = e3d_mesh:triangulate_face(E3dface, Vcoords),
1199    lists:append([FVs || #e3d_face{vs=FVs} <- T3dfaces]).
1200
1201map_oes([[A,B,Face]|OEs], Vs0, Cnt, Face) ->
1202    MA = find_index(A, Vs0, Cnt),
1203    MB = find_index(B, Vs0, Cnt),
1204    [[MA,MB,Face]|map_oes(OEs, Vs0, Cnt, Face)];
1205map_oes([Other|OEs], Vs0, Cnt, Face) ->
1206    [Other|map_oes(OEs, Vs0, Cnt, Face)];
1207map_oes([], _, _, _) -> [].
1208
1209find_index(Val, [Val|_], Pos) -> Pos;
1210find_index(Val, [_|R], Pos) -> find_index(Val, R, Pos-1).
1211
1212fix_normals(Vs,Vmap,VsI,Ns0) -> %% can be different order
1213    fix_normals(Vs,n_zip(VsI,Ns0),Vmap).
1214fix_normals([V|R],Ns,Vmap) ->
1215    [find(map_vertex(V,Vmap),Ns)|fix_normals(R,Ns,Vmap)];
1216fix_normals([],_,_) -> [].
1217
1218%%%% Tangent calc
1219
1220calc_tang_vecs(_,_,_,_,[],Ts,_) -> Ts;
1221calc_tang_vecs(Vs,VsIds,Coords,UVCoords,Normals,Ts0,Reqs) ->
1222    %% io:format("Vs ~p ~n",[Vs]),
1223    %% io:format("Coords ~p ~n",[Coords]),
1224    %% io:format("UV ~p ~n",[UVCoords]),
1225    %% io:format("Normals ~p ~n",[Normals]),
1226    case lists:member(binormal,Reqs) of
1227	true ->
1228	    N = e3d_vec:norm(e3d_vec:average(Normals)),
1229	    calc_tang_vecs_1([V+1||V <- Vs], VsIds, Coords, UVCoords, N, Ts0);
1230	false ->
1231	    Ts0
1232    end.
1233
1234calc_tang_vecs_1([Vi1,Vi2,Vi3|Vis], VsIds, Coords, UVCoords, N, {Ts, F2V}) ->
1235    {X1,Y1,Z1} = e3d_vec:sub(lists:nth(Vi2,Coords), lists:nth(Vi1,Coords)),
1236    {X2,Y2,Z2} = e3d_vec:sub(lists:nth(Vi3,Coords), lists:nth(Vi1,Coords)),
1237    {U1,V1,_} = lists:nth(Vi1, UVCoords),
1238    {U2,V2,_} = lists:nth(Vi2, UVCoords),
1239    {U3,V3,_} = lists:nth(Vi3, UVCoords),
1240    S1 = U2-U1,
1241    S2 = U3-U1,
1242    T1 = V2-V1,
1243    T2 = V3-V1,
1244    Vs = [lists:nth(Vi1, VsIds),lists:nth(Vi2,VsIds),lists:nth(Vi3,VsIds)],
1245    try
1246	F = 1.0 / (S1*T2 - S2*T1),
1247	Tangent = {F*(T2*X1-T1*X2), F*(T2*Y1-T1*Y2), F*(T2*Z1-T1*Z2)},
1248	BiTangent = {F*(S1*X2-S2*X1), F*(S1*Y2-S2*Y1), F*(S1*Z2-S2*Z1)},
1249	H = case e3d_vec:dot(e3d_vec:cross(N, Tangent), BiTangent) < 0.0 of
1250		true  -> 1;
1251		false -> -1
1252	    end,
1253	Tss0 = {add_tangent(Vs, Tangent, BiTangent, Ts), [{N,H,Vs}|F2V]},
1254	calc_tang_vecs_1(Vis, VsIds, Coords, UVCoords, N, Tss0)
1255    catch _:badarith ->
1256	    Tss1 = {Ts, [{N,0,Vs}|F2V]},
1257	    calc_tang_vecs_1(Vis, VsIds, Coords, UVCoords, N, Tss1)
1258    end;
1259calc_tang_vecs_1([], _, _, _, _, Tss) ->
1260    Tss.
1261
1262add_tangent([V|Vs], Tangent, BiTangent, Ts) ->
1263    {T0, B0} = array:get(V,Ts),
1264    T1 = e3d_vec:add(T0, Tangent),
1265    T2 = e3d_vec:add(B0, BiTangent),
1266    add_tangent(Vs, Tangent, BiTangent, array:set(V, {T1, T2}, Ts));
1267add_tangent([], _, _, Ts) -> Ts.
1268
1269build_tangents({VsTs0, RevF2V}) ->
1270    VsTs = array:map(fun(_V, {T, BT}) -> {e3d_vec:norm(T), e3d_vec:norm(BT)} end, VsTs0),
1271    build_tangents(lists:reverse(RevF2V), VsTs, <<>>).
1272
1273build_tangents([{N, H, Face}|Fs], Ts, Bin0) ->
1274    Bin = add_tangents1(Face, Ts, H, N, undefined, Bin0),
1275    build_tangents(Fs, Ts, Bin);
1276build_tangents([], _, Bin) -> Bin.
1277
1278add_tangents1([V|Vs], Ts, H0, N, Prev, Bin0) ->
1279    case array:get(V, Ts) of
1280	{{0.0, 0.0, 0.0}, BiT} ->
1281	    {Tan = {X,Y,Z}, H} = get_tangent(Prev, BiT, H0, N),
1282	    Bin = <<Bin0/binary, X:?F32,Y:?F32,Z:?F32, H:?F32>>,
1283	    add_tangents1(Vs, Ts, H, N, Tan, Bin);
1284	{Tan = {X,Y,Z}, _} when H0 /= 0 ->
1285	    Bin = <<Bin0/binary, X:?F32,Y:?F32,Z:?F32, H0:?F32>>,
1286	    add_tangents1(Vs, Ts, H0, N, Tan, Bin);
1287	{Tan = {X,Y,Z}, BiT} ->
1288	    H = case e3d_vec:dot(e3d_vec:cross(N, Tan), BiT) < 0.0 of
1289		    true  -> 1;
1290		    false -> -1
1291		end,
1292	    Bin = <<Bin0/binary, X:?F32,Y:?F32,Z:?F32, H:?F32>>,
1293	    add_tangents1(Vs, Ts, H, N, Tan, Bin)
1294    end;
1295add_tangents1([], _, _, _, _, Bin) -> Bin.
1296
1297get_tangent(undefined, {0.0,0.0,0.0}, H0, N) ->
1298    H = if H0 =:= 0 -> -1; true -> H0 end,
1299    {cross_axis(N), H};
1300get_tangent(undefined, BiT, 0, N) ->
1301    T = e3d_vec:cross(BiT, N),
1302    H = case e3d_vec:dot(e3d_vec:cross(N, T), BiT) < 0.0 of
1303	    true  -> 1;
1304	    false -> -1
1305	end,
1306    {T, H};
1307get_tangent(undefined, BiT, H, N) ->
1308    {e3d_vec:cross(BiT, N), H};
1309get_tangent(Prev, _, H, _) ->
1310    {Prev, H}.
1311
1312cross_axis(N = {NX,NY,NZ}) ->
1313    try
1314	V2 = case abs(NX) > abs(NY) of
1315		 true ->
1316		     ILen = 1.0 / math:sqrt(NX*NX+NZ*NZ),
1317		     {-NZ*ILen, 0.0, NX * ILen};
1318		 false ->
1319		     ILen = 1.0 / math:sqrt(NY*NY+NZ*NZ),
1320		     {0.0, NZ*ILen, -NY * ILen}
1321	     end,
1322	e3d_vec:cross(N, V2)
1323    catch _:badarith ->
1324	    {1.0, 0.0,0.0}
1325    end.
1326
1327%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1328
1329fix_vc(Vs, Face, We, Vmap) ->
1330    try
1331	Uvc = wings_va:face_attr([vertex|color], Face, We),
1332	fix_vc1(Vs, Uvc, Vmap, [])
1333    catch error:_ ->
1334	    fix_vc1(Vs, [], Vmap, [])
1335    end.
1336
1337fix_vc1([V|Vs], Uvc, Vmap, Acc) ->
1338    Val = case find(map_vertex(V, Vmap), Uvc) of
1339	      {_,_,_}=Color ->  Color;
1340	      _ -> {1.0,1.0,1.0}
1341	  end,
1342    fix_vc1(Vs, Uvc, Vmap, [Val|Acc]);
1343fix_vc1([], _, _, Acc) -> reverse(Acc).
1344
1345find(V, [[V|Info]|_R]) -> Info;
1346find(V, [_|R]) -> find(V,R);
1347find(_, []) -> none.
1348
1349n_zip([[V|_]|R1],[N|R2]) ->
1350    [[V|N]|n_zip(R1,R2)];
1351n_zip([],[]) -> [].
1352
1353
1354fix(OK = {_,_,_}, false) -> OK;
1355fix(OK = {_,_,_,_}, true) -> OK;
1356fix({R,G,B,_}, false) -> {R,G,B};
1357fix({R,G,B}, true) -> {R,G,B,1.0}.
1358
1359setup_normals(We = #we{fs=Ftab}) ->
1360    FN0	= [{Face,wings_face:normal(Face, We)} || Face <- gb_trees:keys(Ftab)],
1361    Ns = wings_we:normals(FN0, We, none),
1362    gb_trees:from_orddict(sort(Ns)).
1363
1364outer_verts(We = #we{es=Etab}) ->
1365    Fs = wings_we:visible(We),
1366    Outer = auv_util:outer_edges(Fs,We,false),
1367    Verts = fun({Edge,Face}) ->
1368		    #edge{vs=Va,ve=Vb} = array:get(Edge, Etab),
1369		    [Va,Vb,Face]
1370	    end,
1371    lists:map(Verts, Outer).
1372
1373%% Start with 64 bytes so that binary will be reference counted
1374%% and not on the process heap spent hours debugging this.. :-(
1375to_bin(List, uv) -> to_bin3to2(List,[<<0:512>>]);
1376to_bin(List, pos) -> to_bin3(List,[<<0:512>>]);
1377to_bin(List, vertex) -> to_bin3(List,[<<0:512>>]).  %% Vertex colors
1378
1379to_bin3([{A,B,C}|R],Acc) ->
1380    to_bin3(R,[<<A:32/native-float,B:32/native-float,C:32/native-float>>|Acc]);
1381to_bin3([],Acc) -> list_to_binary(Acc).
1382
1383to_bin3to2([{A,B,_}|R],Acc) ->
1384    to_bin3to2(R,[<<A:32/native-float,B:32/native-float>>|Acc]);
1385to_bin3to2([],Acc) -> list_to_binary(Acc).
1386
1387%% Workaround for ATI: gl:'end' doesn't bite for line loop/strip...
1388vs_lines([A|R=[B|_]],Last) ->
1389    [A,B|vs_lines(R,Last)];
1390vs_lines([B],Last) ->
1391    [B,Last].
1392
1393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1394%% Builtin Shader Passes
1395%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1396
1397get_passes(Passes,Shaders) ->
1398    lists:foldr(fun(Pass,Acc) ->
1399			case pass(Pass,Shaders) of
1400			    Fun when is_function(Fun) ->
1401				[Fun|Acc];
1402			    _ ->
1403				Acc
1404			end
1405		end, [], Passes).
1406
1407pass({ignore,_},_) ->  ignore;
1408pass({auv_background,[auv_background, {type_sel,color},_,Color]},_) ->
1409    fun(_Geom,_) ->
1410	    {R,G,B,A} = fix(Color,true),
1411	    gl:clearColor(R,G,B,A),
1412	    gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT)
1413    end;
1414pass({auv_background,[auv_background, {type_sel,image},{_Name,Id},Color]},_) ->
1415    fun(_Geom,_) ->
1416	    {R,G,B,A} = fix(Color,true),
1417	    gl:clearColor(R,G,B,A),
1418	    gl:clear(?GL_COLOR_BUFFER_BIT bor ?GL_DEPTH_BUFFER_BIT),
1419	    case wings_image:txid(Id) of
1420		none -> ignore;
1421		TxId ->
1422 		    gl:enable(?GL_TEXTURE_2D),
1423		    gl:bindTexture(?GL_TEXTURE_2D, TxId),
1424		    draw_texture_square(),
1425		    gl:disable(?GL_TEXTURE_2D)
1426	    end
1427    end;
1428pass({auv_background, _},Sh) ->
1429    pass({auv_background, ?OPT_BG},Sh);
1430
1431pass({auv_edges, [auv_edges,all_edges,Color,Width,_UseMat]},_) ->
1432    R=fun(#chart{fs=Fs}) ->
1433	      Draw = fun(#fs{vse=Vs}) ->
1434			     Patched = vs_lines(Vs,hd(Vs)),
1435			     wings_gl:drawElements(?GL_LINES,length(Patched),
1436                                                   ?GL_UNSIGNED_INT,Patched)
1437                     end,
1438	      foreach(Draw,Fs)
1439      end,
1440    fun(#ts{vbo={call,_,{vbo,Vbo}}, charts=Charts},_) ->
1441	    gl:disable(?GL_DEPTH_TEST),
1442	    gl:color3fv(Color),
1443	    gl:lineWidth(float(Width)),
1444            gl:bindBuffer(?GL_ARRAY_BUFFER, Vbo),
1445            gl:vertexPointer(2, ?GL_FLOAT, 0, 0),
1446	    gl:enableClientState(?GL_VERTEX_ARRAY),
1447	    foreach(R, Charts),
1448	    gl:disableClientState(?GL_VERTEX_ARRAY)
1449    end;
1450pass({auv_edges, [auv_edges,border_edges,Color,Width,UseMat]},_) ->
1451    R= fun(#chart{oes=Es0}) ->
1452	       Es = foldl(fun([A,B,_],Acc) -> [A,B|Acc] end, [], Es0),
1453	       wings_gl:drawElements(?GL_LINES,length(Es),?GL_UNSIGNED_INT,Es)
1454       end,
1455    fun(#ts{vbo={call,_,{vbo,Vbo}}, charts=Charts},_) ->
1456	    gl:color3fv(Color),
1457	    gl:lineWidth(float(Width)),
1458            gl:bindBuffer(?GL_ARRAY_BUFFER, Vbo),
1459            gl:vertexPointer(2, ?GL_FLOAT, 0, 0),
1460	    gl:enableClientState(?GL_VERTEX_ARRAY),
1461	    gl:disable(?GL_DEPTH_TEST),
1462	    case UseMat of
1463		true ->
1464		    gl:enableClientState(?GL_COLOR_ARRAY),
1465		    foreach(R, Charts),
1466		    gl:disableClientState(?GL_COLOR_ARRAY);
1467		_ ->
1468		    foreach(R, Charts)
1469	    end,
1470	    gl:disableClientState(?GL_VERTEX_ARRAY)
1471    end;
1472pass({auv_edges, _},Sh) ->
1473    pass({auv_edges, ?OPT_EDGES},Sh);
1474
1475pass({auv_faces,[_]},_) ->
1476    fun(#ts{vbo={call,EnableVbo,{vbo,_Vbo}}, charts=Charts}, _) ->
1477	    gl:disable(?GL_DEPTH_TEST),
1478	    gl:disable(?GL_ALPHA_TEST),
1479	    gl:enable(?GL_BLEND),
1480	    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
1481	    R = fun(#fs{vs=Vs}) ->
1482			wings_gl:drawElements(?GL_TRIANGLES,length(Vs),?GL_UNSIGNED_INT,Vs)
1483		end,
1484	    All = fun(_) -> foreach(fun(#chart{fs=Fs}) -> foreach(R, Fs) end, Charts) end,
1485            EnableVbo(All, #{})
1486    end;
1487pass({auv_faces, _},Sh) ->
1488    pass({auv_faces,?OPT_FACES},Sh);
1489
1490pass({{shader,Id}, Opts},{Sh,Compiled}) ->
1491    shader_pass(lists:keysearch(Id,#sh.id,Sh),
1492		lists:keysearch(Id,1,Compiled),Opts);
1493pass({_R, _O},_) ->
1494    io:format("AUV: ~p:~p: Unknown Render Pass (~p) or options (~p) ~n",
1495	      [?MODULE,?LINE,_R,_O]),
1496    ignore.
1497
1498shader_pass(Shader={value,#sh{id=Id, def=Def}},Prog,[]) when Def /= [] ->
1499    shader_pass(Shader, Prog, [{shader,Id}|reverse(Def)]);
1500shader_pass(_,false,_) ->
1501    io:format("AUV: No shader program found skipped ~p~n", [?LINE]),
1502    ignore;
1503shader_pass(false,_,_) ->
1504    io:format("AUV: Not shader found skipped ~p~n", [?LINE]),
1505    ignore;
1506shader_pass({value,#sh{id=Id, args=Args, tex_units=TexUnits}},
1507	    {value,{_,Prog}}, [{shader,Id}|Opts]) ->
1508    fun(Ts = #ts{vbo={call,EnableVbo,_}, charts=Charts}, Config) ->
1509	    gl:disable(?GL_DEPTH_TEST),
1510	    gl:disable(?GL_ALPHA_TEST),
1511	    gl:enable(?GL_BLEND),
1512	    gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
1513	    wings_gl:use_prog(Prog),
1514	    try
1515		Conf = Config#sh_conf{prog=Prog,ts=Ts},
1516		PerChartUniF = shader_uniforms(reverse(Args),Opts,Conf),
1517		case send_texture(reverse(Args),Opts) of
1518		    true ->
1519			gl:enable(?GL_TEXTURE_2D),
1520			gl:disable(?GL_BLEND),
1521			draw_texture_square();
1522		    _ ->
1523                        DC = fun(Chart = #chart{fs=Fas}) ->
1524                                     PerFaceUniF = sh_uniforms(PerChartUniF,Chart,Conf),
1525                                     R = fun(Face = #fs{vs=Vss}) ->
1526                                                 sh_uniforms(PerFaceUniF,Face,Conf),
1527                                                 wings_gl:drawElements(?GL_TRIANGLES,length(Vss),
1528                                                                       ?GL_UNSIGNED_INT,Vss)
1529                                         end,
1530                                     foreach(R,Fas)
1531                             end,
1532                        EnableVbo(fun(_) -> foreach(DC, Charts) end, #{})
1533                end
1534            catch throw:What ->
1535                    io:format("AUV: ERROR ~s ~n",[What]);
1536		_:What:Stack ->
1537		    io:format("AUV: Internal ERROR ~p:~n~p ~n",[What,Stack])
1538	    after
1539                wings_gl:use_prog(0),
1540                gl:disable(?GL_TEXTURE_2D),
1541                gl:disableClientState(?GL_VERTEX_ARRAY),
1542                gl:disableClientState(?GL_NORMAL_ARRAY),
1543                gl:clientActiveTexture(?GL_TEXTURE2),
1544                gl:disableClientState(?GL_TEXTURE_COORD_ARRAY),
1545                gl:clientActiveTexture(?GL_TEXTURE1),
1546                gl:disableClientState(?GL_TEXTURE_COORD_ARRAY),
1547                gl:clientActiveTexture(?GL_TEXTURE0),
1548                disable_tex_units(TexUnits),
1549                gl:activeTexture(?GL_TEXTURE0)
1550	    end
1551    end.
1552
1553send_texture([{auv,{auv_send_texture,_, _Def0}}|_],
1554	     [{auv_send_texture,Def}|_]) ->
1555    Def;
1556send_texture([{auv,{auv_send_texture,_, _Def0}}|_],
1557	     [Def|_]) ->
1558    Def;
1559send_texture([{auv,_}|Next], Opts) ->
1560    send_texture(Next,Opts);
1561send_texture([_|Next], [_|Opts]) ->
1562    send_texture(Next,Opts);
1563send_texture([],_) -> false.
1564
1565
1566shader_uniforms([A|As], [O|Opts]=Opts0, Conf) ->
1567    Res = shader_uniform(A,O,Conf),
1568    ?ERROR == error andalso io:format("~p~n", [A]),
1569    case Res of
1570        ok -> shader_uniforms(As,Opts,Conf);
1571        no_opt -> shader_uniforms(As,Opts0,Conf);
1572        {keep, Keep} -> [Keep|shader_uniforms(As,Opts0,Conf)]
1573    end;
1574shader_uniforms([], [], _) ->
1575    [].
1576
1577shader_uniform({uniform,color,Name,_,_}, Val, Conf) ->
1578    wings_gl:set_uloc(Conf#sh_conf.prog, Name,Val),
1579    ok;
1580shader_uniform({uniform,float,Name,_,_},Val,Conf) ->
1581    wings_gl:set_uloc(Conf#sh_conf.prog, Name, Val),
1582    ok;
1583shader_uniform({uniform,menu,Name,_,_}, Vals,Conf)
1584  when is_list(Vals) ->
1585    Loc = wings_gl:uloc(Conf#sh_conf.prog,Name),
1586    foldl(fun(Val,Cnt) when is_integer(Val) ->
1587                gl:uniform1i(Loc+Cnt,Val),Cnt+1;
1588             (Val,Cnt) ->
1589                gl:uniform1f(Loc+Cnt,Val),Cnt+1
1590          end,0,Vals),
1591    ok;
1592shader_uniform({uniform,menu,Name,_,_},Vals,Conf)
1593    when is_integer(Vals) ->
1594    Loc = wings_gl:uloc(Conf#sh_conf.prog,Name),
1595    gl:uniform1i(Loc,Vals),
1596    ok;
1597shader_uniform({uniform,bool,Name,_,_},Val,Conf) ->
1598    Loc = wings_gl:uloc(Conf#sh_conf.prog,Name),
1599    BoolF = if Val -> 1.0; true -> 0.0 end,
1600    gl:uniform1f(Loc, BoolF),
1601    ok;
1602shader_uniform({uniform,{slider,_,_},Name,_,_},Val,Conf) ->
1603    Loc = wings_gl:uloc(Conf#sh_conf.prog,Name),
1604	if is_integer(Val) ->
1605			gl:uniform1i(Loc,Val);
1606		true ->
1607			gl:uniform1f(Loc,Val)
1608	end,
1609    ok;
1610shader_uniform({uniform,{image,Unit},Name,_,_},{_,Id},Conf) ->
1611    Loc = wings_gl:uloc(Conf#sh_conf.prog,Name),
1612    gl:activeTexture(?GL_TEXTURE0 + Unit),
1613    TxId = wings_image:txid(Id),
1614    gl:bindTexture(?GL_TEXTURE_2D, TxId),
1615    gl:uniform1i(Loc, Unit),
1616    ok;
1617shader_uniform({auv,{auv_bg,Unit}},_Opts,Conf) ->
1618    Loc = wings_gl:uloc(Conf#sh_conf.prog, "auv_bg"),
1619    gl:activeTexture(?GL_TEXTURE0),
1620    gl:bindTexture(?GL_TEXTURE_2D, Conf#sh_conf.fbo_r),
1621    gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, ?GL_NEAREST),
1622    gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, ?GL_NEAREST),
1623    gl:uniform1i(Loc, Unit),
1624    no_opt;
1625shader_uniform({auv,auv_texsz},_Opts,Conf = #sh_conf{texsz={W,H}}) ->
1626    Loc = wings_gl:uloc(Conf#sh_conf.prog,"auv_texsz"),
1627    gl:uniform2f(Loc,float(W),float(H)),
1628    no_opt;
1629shader_uniform({auv,{auv_send_texture,_,_}},_Val,_Conf) ->
1630    ok;
1631shader_uniform({auv,auv_bbpos3d},_Opts,Conf) ->
1632    Loc = wings_gl:uloc(Conf#sh_conf.prog,"auv_bbpos3d"),
1633    [{MinX,MinY,MinZ},{MaxX,MaxY,MaxZ}] = (Conf#sh_conf.ts)#ts.bb,
1634    gl:uniform3f(Loc,MinX,MinY,MinZ),
1635    gl:uniform3f(Loc+1,MaxX,MaxY,MaxZ),
1636    no_opt;
1637shader_uniform({auv,What},_Opts,_Conf) ->
1638    {keep, What}.
1639
1640%% Per Face or per Chart uniforms
1641sh_uniforms([auv_bbpos2d|R],Chart=#chart{bb_uv=BB},Conf) ->
1642    Loc = wings_gl:uloc(Conf#sh_conf.prog,"auv_bbpos2d"),
1643    [{MinX,MinY,_},{MaxX,MaxY,_}] = BB,
1644    gl:uniform2f(Loc,MinX,MinY),
1645    gl:uniform2f(Loc+1,MaxX,MaxY),
1646    sh_uniforms(R,Chart,Conf);
1647sh_uniforms([_|R],Chart,Conf) ->
1648    sh_uniforms(R,Chart,Conf);
1649sh_uniforms([],_,_) ->
1650    [].
1651
1652disable_tex_units(Unit) when Unit > 0 ->
1653    gl:activeTexture(?GL_TEXTURE0 + Unit-1),
1654    gl:bindTexture(?GL_TEXTURE_2D, 0),
1655    disable_tex_units(Unit-1);
1656disable_tex_units(_) -> ok.
1657
1658delete_shaders(List) ->
1659    foreach(fun({_,Prog}) -> gl:deleteProgram(Prog) end, List).
1660
1661get_requirements(Shaders) ->
1662    lists:foldl(fun(#sh{reqs=List},Acc) ->
1663			List ++ Acc
1664		end, [], Shaders).
1665
1666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1667%% Shader loading/handling
1668%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1669
1670shaders() ->
1671    case have_shaders() of
1672	true -> load_shaders_cfg();
1673	false -> []
1674    end.
1675
1676have_shaders() ->
1677    wings_gl:support_shaders() andalso wings_gl:is_ext('GL_EXT_framebuffer_object').
1678
1679load_shaders_cfg() ->
1680    Path  = filename:dirname(code:which(?MODULE)),
1681    Files = filelib:wildcard("wpc_*.auv", Path),
1682    lists:keysort(#sh.name, load_configs(Files,Path, [])).
1683
1684load_configs([Name|Fs], Path, Acc) ->
1685    File = filename:join(Path,Name),
1686    case file:consult(File) of
1687	{ok,Info} ->
1688	    Id = list_to_atom(filename:basename(Name,".auv")++"_auv"),
1689	    Sh = #sh{file=File,id=Id},
1690	    load_configs(Fs,Path,parse_sh_info(Info,Sh,1,Acc));
1691	Other ->
1692	    io:format("AUV: Couldn't load ~p ~n",[File]),
1693	    io:format("     Error: ~p ~n",[Other]),
1694	    load_configs(Fs,Path,Acc)
1695    end;
1696load_configs([],_Path,Acc) ->
1697    Acc.
1698
1699parse_sh_info([{name,Name}|Opts],Sh,NI,Acc) ->
1700    parse_sh_info(Opts, Sh#sh{name=Name},NI,Acc);
1701parse_sh_info([{vertex_shader,Name}|Opts],Sh,NI,Acc) ->
1702    parse_sh_info(Opts, Sh#sh{vs=Name},NI, Acc);
1703parse_sh_info([{fragment_shader,Name}|Opts],Sh,NI,Acc) ->
1704    parse_sh_info(Opts, Sh#sh{fs=Name},NI, Acc);
1705parse_sh_info([{requires,List}|Opts],Sh,NI,Acc) when is_list(List) ->
1706    parse_sh_info(Opts, Sh#sh{reqs=List},NI, Acc);
1707parse_sh_info([{preview,Opt}|Opts],Sh,NI,Acc) ->
1708    parse_sh_info(Opts, Sh#sh{preview=(Opt=/=no)},NI, Acc);
1709parse_sh_info([{auv,auv_bg}|Opts],Sh,NI,Acc) ->
1710    What = {auv,{auv_bg,0}},
1711    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args]},NI,Acc);
1712parse_sh_info([What={auv,auv_texsz}|Opts],Sh,NI,Acc) ->
1713    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args]},NI,Acc);
1714parse_sh_info([What={auv,auv_bbpos2d}|Opts],Sh,NI,Acc) ->
1715    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args]},NI,Acc);
1716parse_sh_info([What={auv,auv_bbpos3d}|Opts],Sh,NI,Acc) ->
1717    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args]},NI,Acc);
1718parse_sh_info([What={auv,{auv_send_texture,L,Def}}|Opts],Sh,NI,Acc)
1719  when is_list(L) ->
1720    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],def=[Def|Sh#sh.def]},
1721		  NI,Acc);
1722parse_sh_info([What={uniform,color,_,Def={_,_,_,_},_}|Opts],Sh,NI,Acc) ->
1723    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],def=[Def|Sh#sh.def]},
1724		  NI,Acc);
1725parse_sh_info([What={uniform,float,_,Def,_}|Opts],Sh,NI,Acc)
1726  when is_number(Def) ->
1727    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],def=[Def|Sh#sh.def]},
1728		  NI,Acc);
1729parse_sh_info([What={uniform,bool,_,Def,_}|Opts],Sh,NI,Acc) ->
1730    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],def=[Def|Sh#sh.def]},
1731		  NI,Acc);
1732parse_sh_info([{uniform,image,Id,Def0,Str}|Opts],Sh,NI,Acc) ->
1733    %% default value must to be an image valid. It can be a file name or an internal
1734    %% image name. Otherwise, it will be created an background image will be used
1735    Def = load_image(Id, Def0),
1736    What = {uniform,{image,NI},Id,Def,Str},
1737    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],def=[Def|Sh#sh.def]},
1738		  NI+1,Acc);
1739parse_sh_info([What={uniform,{slider,F,T},_,Def,_}|Opts],Sh,NI,Acc)
1740  when is_number(F),is_number(T),is_number(Def) ->
1741    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],def=[Def|Sh#sh.def]},
1742		  NI,Acc);
1743parse_sh_info([What={uniform,menu,_,DefKey,List}|Opts],Sh,NI,Acc) ->
1744    case lists:keysearch(DefKey,1, List) of
1745	{value, {_,Def}} ->
1746	    parse_sh_info(Opts, Sh#sh{args=[What|Sh#sh.args],
1747				      def=[Def|Sh#sh.def]},NI,Acc);
1748	false ->
1749	    io:format("AUV: ~p Bad default value ignored menu ~p ~n",
1750		      [Sh#sh.file,What]),
1751	    parse_sh_info(Opts,Sh,NI,Acc)
1752    end;
1753parse_sh_info([_Error|Opts],Sh,NI,Acc) ->
1754    io:format("AUV: In ~p Unknown shader opt ignored ~p",
1755	      [Sh#sh.file,_Error]),
1756    parse_sh_info(Opts,Sh,NI,Acc);
1757parse_sh_info([],Sh,NI,Acc) ->
1758    %% Verify shader here
1759    [Sh#sh{tex_units=NI}|Acc].
1760
1761%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1762%%% Shader compilation
1763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1764
1765compile_shaders(Passes, Available) ->
1766    foldl(fun({{shader,Id},_},Acc) ->
1767		  case lists:keysearch(Id,1,Acc) of
1768		      {value, _} -> Acc; %  Already compiled
1769		      false ->
1770			  compile_shader(Id,lists:keysearch(Id,#sh.id,Available),Acc)
1771		  end;
1772	     (_NormalPass,Acc) ->
1773		  Acc
1774	  end, [], Passes).
1775
1776compile_shader(Id, {value,#sh{name=Name,vs=VsF,fs=FsF}}, Acc) ->
1777    try
1778	Vs = wings_gl:compile(vertex, read_file(VsF)),
1779        Fs = wings_gl:compile(fragment, read_file(FsF)),
1780        Prog = wings_gl:link_prog([Vs,Fs]),
1781	%% io:format("AUV: Shader ´~s´ ok~n", [Name]),
1782	[{Id,Prog}|Acc]
1783    catch throw:What ->
1784	    io:format("AUV: Error ~p ~s ~n",[Name, What]),
1785	    Acc;
1786	_:Err:Stack ->
1787	    io:format("AUV: Internal Error ~s in~n ~p~n",[Err,Stack]),
1788	    Acc
1789    end;
1790compile_shader(Id, false, Acc) ->
1791    io:format("AUV: Error did not find shader ~p ~n",[Id]),
1792    Acc.
1793
1794read_file(Name) ->
1795    Path = filename:dirname(code:which(?MODULE)),
1796    File = filename:join(Path,Name),
1797    case file:read_file(File) of
1798	{ok, Bin} -> Bin;
1799	_ -> throw("Couldn't read file: " ++ File)
1800    end.
1801